diff --git a/.env.ci.example b/.env.ci similarity index 100% rename from .env.ci.example rename to .env.ci diff --git a/.env.local.example b/.env.local.example index 2984803..a8d3835 100644 --- a/.env.local.example +++ b/.env.local.example @@ -1,10 +1,11 @@ +# Local env file assumes Sail is in use. See docker-compose.yml. APP_NAME=kcal APP_ENV=local APP_KEY= APP_DEBUG=true -APP_URL=http://127.0.0.1 +APP_URL=http://kcal.test APP_PORT=8080 -APP_SERVICE=kcal.test +APP_SERVICE=app APP_TIMEZONE=UTC LOG_CHANNEL=stack @@ -17,17 +18,8 @@ DB_DATABASE=kcal DB_USERNAME=kcal DB_PASSWORD=kcal -#REDIS_URL= REDIS_HOST=redis -#REDIS_PASSWORD= REDIS_PORT=6379 -#REDIS_DB= - -#SCOUT_DRIVER=null - -#SCOUT_DRIVER=algolia -#ALGOLIA_APP_ID= -#ALGOLIA_SECRET= SCOUT_DRIVER=elastic ELASTIC_HOST=elasticsearch:9200 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 72a48a5..a8adac2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: run: composer install --no-progress --no-interaction - name: Generate app key run: | - php -r "file_exists('.env') || copy('.env.ci.example', '.env');" + php -r "file_exists('.env') || copy('.env.ci', '.env');" php artisan key:generate - name: Run tests run: vendor/bin/paratest --coverage-clover build/logs/clover.xml diff --git a/_ide_helper.php b/_ide_helper.php index 2517b18..44e01a6 100644 --- a/_ide_helper.php +++ b/_ide_helper.php @@ -3,7 +3,7 @@ /** * A helper file for Laravel, to provide autocomplete information to your IDE - * Generated for Laravel 8.35.1. + * Generated for Laravel 8.36.2. * * This file should not be included in your code, only analyzed by your IDE! * @@ -2773,7 +2773,7 @@ /** * Dispatch a command to its appropriate handler in the current process. * - * Queuable jobs will be dispatched to the "sync" queue. + * Queueable jobs will be dispatched to the "sync" queue. * * @param mixed $command * @param mixed $handler @@ -15217,6 +15217,16 @@ * * @static */ + public static function censorRequestBodyFields($fieldNames) + { + /** @var \Facade\FlareClient\Flare $instance */ + return $instance->censorRequestBodyFields($fieldNames); + } + /** + * + * + * @static + */ public static function createReport($throwable) { /** @var \Facade\FlareClient\Flare $instance */ diff --git a/app/Http/Controllers/JournalEntryController.php b/app/Http/Controllers/JournalEntryController.php index 44b70d2..2f279dc 100644 --- a/app/Http/Controllers/JournalEntryController.php +++ b/app/Http/Controllers/JournalEntryController.php @@ -20,6 +20,8 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Pluralizer; +use Illuminate\Support\Str; class JournalEntryController extends Controller { @@ -156,72 +158,56 @@ class JournalEntryController extends Controller /** @var \App\Models\JournalEntry[] $entries */ $entries = []; $entry_key = 0; + $group_entries = isset($input['group_entries']) && (bool) $input['group_entries']; // TODO: Improve efficiency. Potential for lots of queries here... foreach ($ingredients as $ingredient) { // Set entry key (combined date and meal or individual entries). - if (isset($input['group_entries']) && (bool) $input['group_entries']) { + if ($group_entries) { $entry_key = "{$ingredient['date']}{$ingredient['meal']}"; } else { $entry_key++; } - // Prepare entry values. + // Get an existing entry (when grouping) or create a new one. $entries[$entry_key] = $entries[$entry_key] ?? JournalEntry::make([ 'date' => $ingredient['date'], 'meal' => $ingredient['meal'], ])->user()->associate(Auth::user()); + $entry = &$entries[$entry_key]; // Calculate amounts based on ingredient type. + $item = NULL; + $amount = Number::floatFromString($ingredient['amount']); if ($ingredient['type'] == Food::class) { $item = Food::whereId($ingredient['id'])->first(); - $nutrient_multiplier = Nutrients::calculateFoodNutrientMultiplier( - $item, - Number::floatFromString($ingredient['amount']), - $ingredient['unit'] - ); + $nutrient_multiplier = Nutrients::calculateFoodNutrientMultiplier($item, $amount, $ingredient['unit']); foreach (Nutrients::all()->pluck('value') as $nutrient) { - $entries[$entry_key]->{$nutrient} += $item->{$nutrient} * $nutrient_multiplier; + $entry->{$nutrient} += $item->{$nutrient} * $nutrient_multiplier; } - $entries[$entry_key]->foods->add($item); + $entry->foods->add($item); } elseif ($ingredient['type'] == Recipe::class) { $item = Recipe::whereId($ingredient['id'])->first(); foreach (Nutrients::all()->pluck('value') as $nutrient) { - $entries[$entry_key]->{$nutrient} += Nutrients::calculateRecipeNutrientAmount( - $item, - $nutrient, - Number::floatFromString($ingredient['amount']), - $ingredient['unit'] - ); + $entry->{$nutrient} += Nutrients::calculateRecipeNutrientAmount($item, $nutrient, $amount, $ingredient['unit']); } - $entries[$entry_key]->recipes->add($item); - } - else { - return back()->withInput()->withErrors("Invalid ingredient type {$ingredient['type']}."); + $entry->recipes->add($item); } - // Set entry summary. - $unit = $ingredient['unit']; - if ($item instanceof Food) { - if ($unit === 'serving') { - if (empty($item->serving_unit) && empty($item->serving_unit_name)) { - $unit = null; - } - elseif (!empty($item->serving_unit_name)) { - $unit = $item->serving_unit_formatted; - } - } + // Add to summary. + if (!empty($entry->summary)) { + $entry->summary .= '; '; } - $entries[$entry_key]->summary .= (!empty($entries[$entry_key]->summary) ? ', ' : null); - $entries[$entry_key]->summary .= "{$ingredient['amount']} {$unit} {$item->name}"; + $entry->summary .= $this->createIngredientSummary($ingredient, $item, $amount); } - foreach ($entries as $entry) { - $entry->save(); - $entry->user->save(); - $entry->foods()->saveMany($entry->foods); - $entry->recipes()->saveMany($entry->recipes); + // Save all new entries. + foreach ($entries as $new_entry) { + $new_entry->save(); + $new_entry->user->save(); + $new_entry->foods()->saveMany($new_entry->foods); + $new_entry->recipes()->saveMany($new_entry->recipes); } $count = count($entries); @@ -236,6 +222,65 @@ class JournalEntryController extends Controller return redirect()->route('journal-entries.index', $parameters); } + /** + * Attempt to create a coherent summary for an entry ingredient. + */ + private function createIngredientSummary(array $ingredient, Food|Recipe $item, float $amount): string { + $name = $item->name; + $unit = $ingredient['unit']; + + // Determine unit with special handling for custom Food units. + if ($item instanceof Food) { + if ($unit === 'serving') { + $no_serving_unit = empty($item->serving_unit) && empty($item->serving_unit_name); + + // If there is no serving unit or the serving unit name is + // exactly the same as the item name don't use a serving + // unit and pluralize the _item_ name. + if ($no_serving_unit || $item->serving_unit_name === $name) { + $unit = null; + $name = Pluralizer::plural($name, $amount); + } + + // If the serving unit name is already _part_ of the item + // name, just keep the defined unit (e.g. name: "tortilla + // chips" and serving name "chips"). + elseif (Str::contains($name, $item->serving_unit_name)) { + $unit = 'serving'; + } + + // If a serving unit name is set, use the formatted serving + // unit name as a base. + elseif (!empty($item->serving_unit_name)) { + $unit = $item->serving_unit_formatted; + } + } + } + + // Pluralize unit with supplied plurals or Pluralizer. + if (Nutrients::units()->has($unit)) { + $value = 'label'; + if ($amount > 1) { + $value = 'plural'; + } + $unit = Nutrients::units()->get($unit)[$value]; + } + else { + $unit = Pluralizer::plural($unit, $amount); + } + + // Add amount, unit, and name to summary. + $amount = Number::rationalStringFromFloat($amount); + $summary = "{$amount} {$unit} {$name}"; + + // Add detail if available. + if (isset($item->detail) && !empty($item->detail)) { + $summary .= ", {$item->detail}"; + } + + return $summary; + } + /** * Store an entry from nutrients. */ diff --git a/app/Models/Food.php b/app/Models/Food.php index 8326d18..59441f2 100644 --- a/app/Models/Food.php +++ b/app/Models/Food.php @@ -170,7 +170,7 @@ final class Food extends Model public function getServingSizeFormattedAttribute(): ?string { $result = null; if (!empty($this->serving_size)) { - $result = Number::fractionStringFromFloat($this->serving_size); + $result = Number::rationalStringFromFloat($this->serving_size); } return $result; } diff --git a/app/Models/IngredientAmount.php b/app/Models/IngredientAmount.php index 3ad350b..58762fc 100644 --- a/app/Models/IngredientAmount.php +++ b/app/Models/IngredientAmount.php @@ -92,7 +92,7 @@ final class IngredientAmount extends Model * Get the amount as a formatted string (e.g. 0.5 = 1/2). */ public function getAmountFormattedAttribute(): string { - return Number::fractionStringFromFloat($this->amount); + return Number::rationalStringFromFloat($this->amount); } /** @@ -103,7 +103,7 @@ final class IngredientAmount extends Model public function getNutrientsSummaryAttribute(): string { $summary = []; foreach (Nutrients::all() as $nutrient) { - $amount = round($this->{$nutrient['value']}(), 2); + $amount = Nutrients::round($this->{$nutrient['value']}(), $nutrient['value']); $summary[] = "{$nutrient['label']}: {$amount}{$nutrient['unit']}"; } return implode(', ', $summary); diff --git a/app/Models/Recipe.php b/app/Models/Recipe.php index 6821c20..309d2b5 100644 --- a/app/Models/Recipe.php +++ b/app/Models/Recipe.php @@ -7,6 +7,7 @@ use App\Models\Traits\Ingredient; use App\Models\Traits\Journalable; use App\Models\Traits\Sluggable; use App\Models\Traits\Taggable; +use App\Support\Nutrients; use ElasticScoutDriverPlus\QueryDsl; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -165,7 +166,7 @@ final class Recipe extends Model implements HasMedia if (empty($this->weight)) { return null; } - return round($this->weight / $this->servings, 2); + return round($this->weight / $this->servings); } /** @@ -209,13 +210,6 @@ final class Recipe extends Model implements HasMedia /** * Add nutrient calculations handling to overloading. - * - * @param string $method - * @param array $parameters - * - * @return mixed - * - * @noinspection PhpMissingParamTypeInspection */ public function __call($method, $parameters): mixed { if (in_array($method, $this->nutrientTotalMethods)) { @@ -223,15 +217,7 @@ final class Recipe extends Model implements HasMedia } elseif (in_array($method, $this->nutrientPerServingMethods)) { $sum = $this->sumNutrient(substr($method, 0, -10)) / $this->servings; - - // Per-serving calculations are rounded, though actual food label - // rounding standards are more complex. - if ($sum > 1) { - return round($sum); - } - else { - return round($sum, 2); - } + return Nutrients::round($sum, substr($method, 0, -10)); } else { return parent::__call($method, $parameters); diff --git a/app/Support/Number.php b/app/Support/Number.php index d5e42d2..8688404 100644 --- a/app/Support/Number.php +++ b/app/Support/Number.php @@ -7,10 +7,10 @@ use Phospr\Fraction; class Number { /** - * Get a float value from a decimal or fraction string. + * Get a float value from a decimal or rational string. * * @param string $value - * Value in decimal or string format. + * Decimal or rational string. * @return float * Float representation of the value. * @@ -27,21 +27,38 @@ class Number } /** - * Get a string faction representation of a float. + * Get a string rational representation of a float. * - * @todo Handle repeating values like 1/3, 2/3, etc. better. - * - * @see https://rosettacode.org/wiki/Convert_decimal_number_to_rational#PHP + * Special handling is used for common cases 1/3 and 2/3 to ensure the + * expected rationals. Other less common rationals (e.g. n/7 or n/9) will + * not be well handled here. * * @param float $value - * Value to convert to string fraction. + * Value to convert to rational string. * @return string - * String fraction. + * Rational string. + * + * @todo Learn maths. */ - public static function fractionStringFromFloat(float $value): string { - $fraction = (string) Fraction::fromFloat($value); - $fraction = str_replace(['33/100', '33333333/100000000'], '1/3', $fraction); - $fraction = str_replace(['67/100', '66666667/100000000'], '2/3', $fraction); - return $fraction; + public static function rationalStringFromFloat(float $value): string { + $decimal = Fraction::fromFloat(($value - floor($value))); + if ($decimal->isSameValueAs(Fraction::fromFloat(1/3))) { + $string = '1/3'; + $whole = floor($value); + if ($whole > 0) { + $string = "{$whole} {$string}"; + } + } + elseif ($decimal->isSameValueAs(Fraction::fromFloat(2/3))) { + $string = '2/3'; + $whole = floor($value); + if ($whole > 0) { + $string = "{$whole} {$string}"; + } + } + else { + $string = (string) Fraction::fromFloat($value); + } + return $string; } } diff --git a/app/Support/Nutrients.php b/app/Support/Nutrients.php index 29717e7..4daa135 100644 --- a/app/Support/Nutrients.php +++ b/app/Support/Nutrients.php @@ -16,6 +16,7 @@ class Nutrients * Each entry has two keys: * - value: Machine name for the unit. * - label: Human-readable name for the unit. + * - plural: Human-readable plural form of the unit name. * - type: Unit type -- matching types can be converted. * * @return \Illuminate\Support\Collection @@ -25,31 +26,37 @@ class Nutrients 'cup' => [ 'value' => 'cup', 'label' => 'cup', + 'plural' => 'cups', 'type' => 'volume', ], 'gram' => [ 'value' => 'gram', 'label' => 'gram', + 'plural' => 'grams', 'type' => 'weight', ], 'oz' => [ 'value' => 'oz', 'label' => 'oz', + 'plural' => 'oz', 'type' => 'weight', ], 'serving' => [ 'value' => 'serving', 'label' => 'serving', + 'plural' => 'servings', 'type' => 'division', ], 'tbsp' => [ 'value' => 'tbsp', 'label' => 'tbsp.', + 'plural' => 'tbsp.', 'type' => 'volume', ], 'tsp' => [ 'value' => 'tsp', 'label' => 'tsp.', + 'plural' => 'tsp.', 'type' => 'volume', ], ]); @@ -188,4 +195,57 @@ class Nutrients throw new \DomainException("Unsupported recipe unit: {$fromUnit}"); } } + + /** + * Round a nutrient amount according to FDA guidelines. + * + * Note: this stays mostly true to the guidelines except that carbohydrates + * and protein are meant to state "less than 1 gram" when the amount is less + * than 1 gram. Instead, this method treats anything less than 1 gram as + * zero. + * + * @url https://labelcalc.com/food-labeling/a-guide-to-using-fda-rounding-rules-for-your-food-label/ + * + * @throws \InvalidArgumentException + */ + public static function round(float $amount, string $nutrient): float { + return match ($nutrient) { + + /* + * Calories: + * - Less than 5 goes to zero. + * - Between 5 and 50 rounds to nearest number divisible by 5. + * - Greater than 50 rounds to nearest number divisible by 10. + */ + 'calories' => ($amount < 5 ? 0 : ($amount <= 50 ? round($amount / 5 ) * 5 : round($amount / 10 ) * 10)), + + /* + * Carbohydrates and protein: + * - Less than 1 goes to zero. + * - Greater than 1 rounds to nearest whole. + */ + 'carbohydrates', 'protein' => ($amount < 1 ? 0 : round($amount)), + + /* + * Cholesterol and fat: + * - Less than 0.5 goes to zero. + * - Between 0.5 and 5 rounds to nearest half. + * - Greater than 5 rounds to nearest whole. + */ + 'cholesterol', 'fat' => ($amount < 0.5 ? 0 : ($amount <= 5 ? round($amount / 5, 1 ) * 5 : round($amount))), + + /* + * Sodium: + * - Less than 5 goes to zero. + * - Between 5 and 140 rounds to nearest number divisible by 5. + * - Greater than 140 rounds to nearest number divisible by 10. + */ + 'sodium' => ($amount < 5 ? 0 : ($amount <= 140 ? round($amount / 5 ) * 5 : round($amount / 10 ) * 10)), + + /* + * Anything else excepts! + */ + default => throw new \InvalidArgumentException("Unrecognized nutrient {$nutrient}.") + }; + } } diff --git a/composer.lock b/composer.lock index cbdc20b..a974517 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "algolia/algoliasearch-client-php", - "version": "2.7.3", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/algolia/algoliasearch-client-php.git", - "reference": "142a382e4649db0cb64d9eb8893872f1a4ba8dd3" + "reference": "d9781147ae433f5bdbfd902497d748d60e70d693" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/algolia/algoliasearch-client-php/zipball/142a382e4649db0cb64d9eb8893872f1a4ba8dd3", - "reference": "142a382e4649db0cb64d9eb8893872f1a4ba8dd3", + "url": "https://api.github.com/repos/algolia/algoliasearch-client-php/zipball/d9781147ae433f5bdbfd902497d748d60e70d693", + "reference": "d9781147ae433f5bdbfd902497d748d60e70d693", "shasum": "" }, "require": { @@ -76,22 +76,22 @@ ], "support": { "issues": "https://github.com/algolia/algoliasearch-client-php/issues", - "source": "https://github.com/algolia/algoliasearch-client-php/tree/2.7.3" + "source": "https://github.com/algolia/algoliasearch-client-php/tree/2.8.0" }, - "time": "2020-12-22T11:27:03+00:00" + "time": "2021-04-07T16:50:58+00:00" }, { "name": "algolia/scout-extended", - "version": "v1.15.0", + "version": "v1.16.0", "source": { "type": "git", "url": "https://github.com/algolia/scout-extended.git", - "reference": "f1da27101b4c88166f9d66d5110b46e1dacb8f1c" + "reference": "1154a57e8049e7d07d51571599ee5aedd106b945" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/algolia/scout-extended/zipball/f1da27101b4c88166f9d66d5110b46e1dacb8f1c", - "reference": "f1da27101b4c88166f9d66d5110b46e1dacb8f1c", + "url": "https://api.github.com/repos/algolia/scout-extended/zipball/1154a57e8049e7d07d51571599ee5aedd106b945", + "reference": "1154a57e8049e7d07d51571599ee5aedd106b945", "shasum": "" }, "require": { @@ -157,9 +157,9 @@ ], "support": { "issues": "https://github.com/algolia/scout-extended/issues", - "source": "https://github.com/algolia/scout-extended/tree/v1.15.0" + "source": "https://github.com/algolia/scout-extended/tree/v1.16.0" }, - "time": "2021-03-17T15:52:17+00:00" + "time": "2021-04-08T15:17:35+00:00" }, { "name": "asm89/stack-cors", @@ -2336,16 +2336,16 @@ }, { "name": "laravel/framework", - "version": "v8.35.1", + "version": "v8.36.2", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "d118c0df39e7524131176aaf76493eae63a8a602" + "reference": "0debd8ad6b5aa1f61ccc73910adf049af4ca0444" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/d118c0df39e7524131176aaf76493eae63a8a602", - "reference": "d118c0df39e7524131176aaf76493eae63a8a602", + "url": "https://api.github.com/repos/laravel/framework/zipball/0debd8ad6b5aa1f61ccc73910adf049af4ca0444", + "reference": "0debd8ad6b5aa1f61ccc73910adf049af4ca0444", "shasum": "" }, "require": { @@ -2500,20 +2500,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2021-03-30T21:34:17+00:00" + "time": "2021-04-07T12:37:22+00:00" }, { "name": "laravel/scout", - "version": "v8.6.0", + "version": "v8.6.1", "source": { "type": "git", "url": "https://github.com/laravel/scout.git", - "reference": "54070f7b68fed15f25e61e68884c4110496b8aa1" + "reference": "7fb1c860a2fd904f0e084a7cc3641eb1448ba278" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/scout/zipball/54070f7b68fed15f25e61e68884c4110496b8aa1", - "reference": "54070f7b68fed15f25e61e68884c4110496b8aa1", + "url": "https://api.github.com/repos/laravel/scout/zipball/7fb1c860a2fd904f0e084a7cc3641eb1448ba278", + "reference": "7fb1c860a2fd904f0e084a7cc3641eb1448ba278", "shasum": "" }, "require": { @@ -2569,7 +2569,7 @@ "issues": "https://github.com/laravel/scout/issues", "source": "https://github.com/laravel/scout" }, - "time": "2021-01-19T15:30:52+00:00" + "time": "2021-04-06T14:35:41+00:00" }, { "name": "laravel/tinker", @@ -3470,16 +3470,16 @@ }, { "name": "opis/closure", - "version": "3.6.1", + "version": "3.6.2", "source": { "type": "git", "url": "https://github.com/opis/closure.git", - "reference": "943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5" + "reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5", - "reference": "943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5", + "url": "https://api.github.com/repos/opis/closure/zipball/06e2ebd25f2869e54a306dda991f7db58066f7f6", + "reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6", "shasum": "" }, "require": { @@ -3529,9 +3529,9 @@ ], "support": { "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.1" + "source": "https://github.com/opis/closure/tree/3.6.2" }, - "time": "2020-11-07T02:01:34+00:00" + "time": "2021-04-09T13:42:10+00:00" }, { "name": "phospr/fraction", @@ -4522,16 +4522,16 @@ }, { "name": "spatie/image", - "version": "1.10.4", + "version": "1.10.5", "source": { "type": "git", "url": "https://github.com/spatie/image.git", - "reference": "7ea129bc7b7521864c5a540e3b1c14ea194316d3" + "reference": "63a963d0200fb26f2564bf7201fc7272d9b22933" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/image/zipball/7ea129bc7b7521864c5a540e3b1c14ea194316d3", - "reference": "7ea129bc7b7521864c5a540e3b1c14ea194316d3", + "url": "https://api.github.com/repos/spatie/image/zipball/63a963d0200fb26f2564bf7201fc7272d9b22933", + "reference": "63a963d0200fb26f2564bf7201fc7272d9b22933", "shasum": "" }, "require": { @@ -4541,7 +4541,7 @@ "league/glide": "^1.6", "php": "^7.2|^8.0", "spatie/image-optimizer": "^1.1", - "spatie/temporary-directory": "^1.0", + "spatie/temporary-directory": "^1.0|^2.0", "symfony/process": "^3.0|^4.0|^5.0" }, "require-dev": { @@ -4575,7 +4575,7 @@ ], "support": { "issues": "https://github.com/spatie/image/issues", - "source": "https://github.com/spatie/image/tree/1.10.4" + "source": "https://github.com/spatie/image/tree/1.10.5" }, "funding": [ { @@ -4587,7 +4587,7 @@ "type": "github" } ], - "time": "2021-03-10T16:11:40+00:00" + "time": "2021-04-07T08:42:24+00:00" }, { "name": "spatie/image-optimizer", @@ -4645,16 +4645,16 @@ }, { "name": "spatie/laravel-medialibrary", - "version": "9.5.0", + "version": "9.5.3", "source": { "type": "git", "url": "https://github.com/spatie/laravel-medialibrary.git", - "reference": "89d1d2e5b4b53137819cc18a166f43edaeaf7e52" + "reference": "ebbc996db457adecc778db6030d22ef72b495d59" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-medialibrary/zipball/89d1d2e5b4b53137819cc18a166f43edaeaf7e52", - "reference": "89d1d2e5b4b53137819cc18a166f43edaeaf7e52", + "url": "https://api.github.com/repos/spatie/laravel-medialibrary/zipball/ebbc996db457adecc778db6030d22ef72b495d59", + "reference": "ebbc996db457adecc778db6030d22ef72b495d59", "shasum": "" }, "require": { @@ -4670,7 +4670,7 @@ "maennchen/zipstream-php": "^1.0|^2.0", "php": "^7.4|^8.0", "spatie/image": "^1.4.0", - "spatie/temporary-directory": "^1.1", + "spatie/temporary-directory": "^1.1|^2.0", "symfony/console": "^4.4|^5.0" }, "conflict": { @@ -4733,7 +4733,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-medialibrary/issues", - "source": "https://github.com/spatie/laravel-medialibrary/tree/9.5.0" + "source": "https://github.com/spatie/laravel-medialibrary/tree/9.5.3" }, "funding": [ { @@ -4745,7 +4745,7 @@ "type": "github" } ], - "time": "2021-03-29T21:40:29+00:00" + "time": "2021-04-08T08:27:49+00:00" }, { "name": "spatie/laravel-tags", @@ -4899,23 +4899,23 @@ }, { "name": "spatie/temporary-directory", - "version": "1.3.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/spatie/temporary-directory.git", - "reference": "f517729b3793bca58f847c5fd383ec16f03ffec6" + "reference": "06fe0f10d068fdf145c9b2235030e568c913bb61" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/f517729b3793bca58f847c5fd383ec16f03ffec6", - "reference": "f517729b3793bca58f847c5fd383ec16f03ffec6", + "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/06fe0f10d068fdf145c9b2235030e568c913bb61", + "reference": "06fe0f10d068fdf145c9b2235030e568c913bb61", "shasum": "" }, "require": { - "php": "^7.2|^8.0" + "php": "^8.0" }, "require-dev": { - "phpunit/phpunit": "^8.0|^9.0" + "phpunit/phpunit": "^9.5" }, "type": "library", "autoload": { @@ -4944,9 +4944,19 @@ ], "support": { "issues": "https://github.com/spatie/temporary-directory/issues", - "source": "https://github.com/spatie/temporary-directory/tree/1.3.0" + "source": "https://github.com/spatie/temporary-directory/tree/2.0.0" }, - "time": "2020-11-09T15:54:21+00:00" + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2021-03-30T19:46:13+00:00" }, { "name": "swiftmailer/swiftmailer", @@ -7552,16 +7562,16 @@ "packages-dev": [ { "name": "barryvdh/laravel-ide-helper", - "version": "v2.9.3", + "version": "v2.10.0", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "2f61602e7a7f88ad29b0f71355b4bb71396e923b" + "reference": "73b1012b927633a1b4cd623c2e6b1678e6faef08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/2f61602e7a7f88ad29b0f71355b4bb71396e923b", - "reference": "2f61602e7a7f88ad29b0f71355b4bb71396e923b", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/73b1012b927633a1b4cd623c2e6b1678e6faef08", + "reference": "73b1012b927633a1b4cd623c2e6b1678e6faef08", "shasum": "" }, "require": { @@ -7630,7 +7640,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-ide-helper/issues", - "source": "https://github.com/barryvdh/laravel-ide-helper/tree/v2.9.3" + "source": "https://github.com/barryvdh/laravel-ide-helper/tree/v2.10.0" }, "funding": [ { @@ -7638,7 +7648,7 @@ "type": "github" } ], - "time": "2021-04-02T14:32:13+00:00" + "time": "2021-04-09T06:17:55+00:00" }, { "name": "barryvdh/reflection-docblock", @@ -8297,16 +8307,16 @@ }, { "name": "facade/flare-client-php", - "version": "1.5.0", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/facade/flare-client-php.git", - "reference": "9dd6f2b56486d939c4467b3f35475d44af57cf17" + "reference": "f2b0969f2d9594704be74dbeb25b201570a98098" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/facade/flare-client-php/zipball/9dd6f2b56486d939c4467b3f35475d44af57cf17", - "reference": "9dd6f2b56486d939c4467b3f35475d44af57cf17", + "url": "https://api.github.com/repos/facade/flare-client-php/zipball/f2b0969f2d9594704be74dbeb25b201570a98098", + "reference": "f2b0969f2d9594704be74dbeb25b201570a98098", "shasum": "" }, "require": { @@ -8350,7 +8360,7 @@ ], "support": { "issues": "https://github.com/facade/flare-client-php/issues", - "source": "https://github.com/facade/flare-client-php/tree/1.5.0" + "source": "https://github.com/facade/flare-client-php/tree/1.6.1" }, "funding": [ { @@ -8358,26 +8368,26 @@ "type": "github" } ], - "time": "2021-03-31T07:32:54+00:00" + "time": "2021-04-08T08:50:01+00:00" }, { "name": "facade/ignition", - "version": "2.7.0", + "version": "2.8.3", "source": { "type": "git", "url": "https://github.com/facade/ignition.git", - "reference": "bdc8b0b32c888f6edc838ca641358322b3d9506d" + "reference": "a8201d51aae83addceaef9344592a3b068b5d64d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/facade/ignition/zipball/bdc8b0b32c888f6edc838ca641358322b3d9506d", - "reference": "bdc8b0b32c888f6edc838ca641358322b3d9506d", + "url": "https://api.github.com/repos/facade/ignition/zipball/a8201d51aae83addceaef9344592a3b068b5d64d", + "reference": "a8201d51aae83addceaef9344592a3b068b5d64d", "shasum": "" }, "require": { "ext-json": "*", "ext-mbstring": "*", - "facade/flare-client-php": "^1.3.7", + "facade/flare-client-php": "^1.6", "facade/ignition-contracts": "^1.0.2", "filp/whoops": "^2.4", "illuminate/support": "^7.0|^8.0", @@ -8435,7 +8445,7 @@ "issues": "https://github.com/facade/ignition/issues", "source": "https://github.com/facade/ignition" }, - "time": "2021-03-30T15:55:38+00:00" + "time": "2021-04-09T20:45:59+00:00" }, { "name": "facade/ignition-contracts", @@ -8929,16 +8939,16 @@ }, { "name": "nunomaduro/collision", - "version": "v5.3.0", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "aca63581f380f63a492b1e3114604e411e39133a" + "reference": "41b7e9999133d5082700d31a1d0977161df8322a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/aca63581f380f63a492b1e3114604e411e39133a", - "reference": "aca63581f380f63a492b1e3114604e411e39133a", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/41b7e9999133d5082700d31a1d0977161df8322a", + "reference": "41b7e9999133d5082700d31a1d0977161df8322a", "shasum": "" }, "require": { @@ -9013,7 +9023,7 @@ "type": "patreon" } ], - "time": "2021-01-25T15:34:13+00:00" + "time": "2021-04-09T13:38:32+00:00" }, { "name": "nunomaduro/larastan", @@ -9533,16 +9543,16 @@ }, { "name": "phpstan/phpstan", - "version": "0.12.82", + "version": "0.12.83", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "3920f0fb0aff39263d3a4cb0bca120a67a1a6a11" + "reference": "4a967cec6efb46b500dd6d768657336a3ffe699f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/3920f0fb0aff39263d3a4cb0bca120a67a1a6a11", - "reference": "3920f0fb0aff39263d3a4cb0bca120a67a1a6a11", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/4a967cec6efb46b500dd6d768657336a3ffe699f", + "reference": "4a967cec6efb46b500dd6d768657336a3ffe699f", "shasum": "" }, "require": { @@ -9573,7 +9583,7 @@ "description": "PHPStan - PHP Static Analysis Tool", "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/0.12.82" + "source": "https://github.com/phpstan/phpstan/tree/0.12.83" }, "funding": [ { @@ -9589,7 +9599,7 @@ "type": "tidelift" } ], - "time": "2021-03-19T06:08:17+00:00" + "time": "2021-04-03T15:35:45+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/database/Factories/IngredientAmountFactory.php b/database/Factories/IngredientAmountFactory.php index 388d543..d8e1c8c 100644 --- a/database/Factories/IngredientAmountFactory.php +++ b/database/Factories/IngredientAmountFactory.php @@ -33,10 +33,11 @@ class IngredientAmountFactory extends Factory $ingredient_unit = 'serving'; } + $amounts = [1/8, 1/4, 1/3, 1/2, 2/3, 3/4, 1, 1 + 1/4, 1 + 1/3, 1 + 1/2, 1 + 2/3, 1 + 3/4, 2, 2 + 1/2, 3]; return [ 'ingredient_id' => $ingredient_factory, 'ingredient_type' => $ingredient_type, - 'amount' => $this->faker->randomElement([1/8, 1/4, 1/2, 3/4, 1, 1.25, 1.5, 1.75, 2, 2.5, 3]), + 'amount' => $this->faker->randomElement($amounts), 'unit' => $ingredient_unit, 'detail' => $this->faker->boolean() ? Words::randomWords('a') : null, 'weight' => $this->faker->numberBetween(0, 50), diff --git a/database/migrations/2021_01_22_212856_create_ingredient_amounts_table.php b/database/migrations/2021_01_22_212856_create_ingredient_amounts_table.php index 7638da3..7b834ff 100644 --- a/database/migrations/2021_01_22_212856_create_ingredient_amounts_table.php +++ b/database/migrations/2021_01_22_212856_create_ingredient_amounts_table.php @@ -18,7 +18,7 @@ class CreateIngredientAmountsTable extends Migration $table->id(); $table->unsignedInteger('ingredient_id'); $table->string('ingredient_type'); - $table->unsignedFloat('amount'); + $table->decimal('amount', 10, 8)->unsigned(); $table->enum('unit', Nutrients::units()->pluck('value')->toArray())->nullable(); $table->string('detail')->nullable(); $table->unsignedInteger('weight'); diff --git a/docker-compose.yml b/docker-compose.yml index 3f442a1..f603791 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ # For more information: https://laravel.com/docs/sail version: '3' services: - kcal.test: + app: build: context: ./vendor/laravel/sail/runtimes/8.0 dockerfile: Dockerfile @@ -18,10 +18,10 @@ services: networks: - sail depends_on: - - mysql + - db - redis - elasticsearch - mysql: + db: image: 'mysql:8.0' ports: - '${DB_PORT:-3306}:3306' @@ -35,6 +35,18 @@ services: - 'mysql-data:/var/lib/mysql' networks: - sail + phpmyadmin: + image: phpmyadmin + restart: always + ports: + - 8080:80 + environment: + PMA_HOST: db + MYSQL_ROOT_PASSWORD: '${DB_PASSWORD:-kcal}' + networks: + - sail + depends_on: + - db elasticsearch: image: 'elasticsearch:7.12.0' environment: diff --git a/phpunit.xml.dist b/phpunit.xml.dist index f28d070..7f0a4e1 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -21,13 +21,14 @@ - - - + + + + diff --git a/public/css/app.css b/public/css/app.css index bc30633..ca45f87 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -1,3 +1,3 @@ /*! tailwindcss v2.0.4 | MIT License | https://tailwindcss.com*/ -/*! modern-normalize v1.0.0 | MIT License | https://github.com/sindresorhus/modern-normalize */:root{-moz-tab-size:4;-o-tab-size:4;tab-size:4}html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji}hr{height:0;color:inherit}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}legend{padding:0}progress{vertical-align:baseline}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}button{background-color:transparent;background-image:none}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}fieldset,ol,ul{margin:0;padding:0}ol,ul{list-style:none}html{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}body{font-family:inherit;line-height:inherit}*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}hr{border-top-width:1px}img{border-style:solid}textarea{resize:vertical}input::-moz-placeholder, textarea::-moz-placeholder{color:#9ca3af}input:-ms-input-placeholder, textarea:-ms-input-placeholder{color:#9ca3af}input::placeholder,textarea::placeholder{color:#9ca3af}[role=button],button{cursor:pointer}table{border-collapse:collapse}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}button,input,optgroup,select,textarea{padding:0;line-height:inherit;color:inherit}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[type=date],[type=email],[type=number],[type=password],[type=search],[type=text],[type=time],[type=url],select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem}[type=date]:focus,[type=email]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,select:focus,textarea:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 transparent);border-color:#2563eb}input::-moz-placeholder, textarea::-moz-placeholder{color:#6b7280;opacity:1}input:-ms-input-placeholder, textarea:-ms-input-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}select{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;color-adjust:exact}[type=checkbox]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0}[type=checkbox]:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 transparent)}[type=checkbox]:checked{background-size:100% 100%;background-position:50%;background-repeat:no-repeat;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3E%3C/svg%3E")}[type=checkbox]:checked,[type=checkbox]:checked:focus,[type=checkbox]:checked:hover{border-color:transparent;background-color:currentColor}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3E%3C/svg%3E");border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:50%;background-repeat:no-repeat}[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{border-color:transparent;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px auto -webkit-focus-ring-color}.prose{color:#374151;max-width:65ch}.prose [class~=lead]{color:#4b5563;font-size:1.25em;line-height:1.6;margin-top:1.2em;margin-bottom:1.2em}.prose a{color:#111827;text-decoration:underline;font-weight:500}.prose strong{color:#111827;font-weight:600}.prose ol[type=A]{--list-counter-style:upper-alpha}.prose ol[type=a]{--list-counter-style:lower-alpha}.prose ol[type=i]{--list-counter-style:lower-roman}.prose ol[type="1"]{--list-counter-style:decimal}.prose ol>li{position:relative;padding-left:1.75em}.prose ol>li:before{content:counter(list-item,var(--list-counter-style,decimal)) ".";position:absolute;font-weight:400;color:#6b7280;left:0}.prose ul>li{position:relative;padding-left:1.75em}.prose ul>li:before{content:"";position:absolute;background-color:#d1d5db;border-radius:50%;width:.375em;height:.375em;top:.6875em;left:.25em}.prose hr{border-color:#e5e7eb;border-top-width:1px;margin-top:3em;margin-bottom:3em}.prose blockquote{font-weight:500;font-style:italic;color:#111827;border-left-width:.25rem;border-left-color:#e5e7eb;quotes:"\201C""\201D""\2018""\2019";margin-top:1.6em;margin-bottom:1.6em;padding-left:1em}.prose blockquote p:first-of-type:before{content:open-quote}.prose blockquote p:last-of-type:after{content:close-quote}.prose h1{color:#111827;font-weight:800;font-size:2.25em;margin-top:0;margin-bottom:.8888889em;line-height:1.1111111}.prose h2{color:#111827;font-weight:700;font-size:1.5em;margin-top:2em;margin-bottom:1em;line-height:1.3333333}.prose h3{font-size:1.25em;margin-top:1.6em;margin-bottom:.6em;line-height:1.6}.prose h3,.prose h4{color:#111827;font-weight:600}.prose h4{margin-top:1.5em;margin-bottom:.5em;line-height:1.5}.prose figure figcaption{color:#6b7280;font-size:.875em;line-height:1.4285714;margin-top:.8571429em}.prose code{color:#111827;font-weight:600;font-size:.875em}.prose code:after,.prose code:before{content:"`"}.prose a code{color:#111827}.prose pre{color:#e5e7eb;background-color:#1f2937;overflow-x:auto;font-size:.875em;line-height:1.7142857;margin-top:1.7142857em;margin-bottom:1.7142857em;border-radius:.375rem;padding:.8571429em 1.1428571em}.prose pre code{background-color:transparent;border-width:0;border-radius:0;padding:0;font-weight:400;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}.prose pre code:after,.prose pre code:before{content:none}.prose table{width:100%;table-layout:auto;text-align:left;margin-top:2em;margin-bottom:2em;font-size:.875em;line-height:1.7142857}.prose thead{color:#111827;font-weight:600;border-bottom-width:1px;border-bottom-color:#d1d5db}.prose thead th{vertical-align:bottom;padding-right:.5714286em;padding-bottom:.5714286em;padding-left:.5714286em}.prose tbody tr{border-bottom-width:1px;border-bottom-color:#e5e7eb}.prose tbody tr:last-child{border-bottom-width:0}.prose tbody td{vertical-align:top;padding:.5714286em}.prose{font-size:1rem;line-height:1.75}.prose p{margin-top:1.25em;margin-bottom:1.25em}.prose figure,.prose img,.prose video{margin-top:2em;margin-bottom:2em}.prose figure>*{margin-top:0;margin-bottom:0}.prose h2 code{font-size:.875em}.prose h3 code{font-size:.9em}.prose ol,.prose ul{margin-top:1.25em;margin-bottom:1.25em}.prose li{margin-top:.5em;margin-bottom:.5em}.prose>ul>li p{margin-top:.75em;margin-bottom:.75em}.prose>ul>li>:first-child{margin-top:1.25em}.prose>ul>li>:last-child{margin-bottom:1.25em}.prose>ol>li>:first-child{margin-top:1.25em}.prose>ol>li>:last-child{margin-bottom:1.25em}.prose ol ol,.prose ol ul,.prose ul ol,.prose ul ul{margin-top:.75em;margin-bottom:.75em}.prose h2+*,.prose h3+*,.prose h4+*,.prose hr+*{margin-top:0}.prose thead th:first-child{padding-left:0}.prose thead th:last-child{padding-right:0}.prose tbody td:first-child{padding-left:0}.prose tbody td:last-child{padding-right:0}.prose>:first-child{margin-top:0}.prose>:last-child{margin-bottom:0}.prose-lg{font-size:1.125rem;line-height:1.7777778}.prose-lg p{margin-top:1.3333333em;margin-bottom:1.3333333em}.prose-lg [class~=lead]{font-size:1.2222222em;line-height:1.4545455;margin-top:1.0909091em;margin-bottom:1.0909091em}.prose-lg blockquote{margin-top:1.6666667em;margin-bottom:1.6666667em;padding-left:1em}.prose-lg h1{font-size:2.6666667em;margin-top:0;margin-bottom:.8333333em;line-height:1}.prose-lg h2{font-size:1.6666667em;margin-top:1.8666667em;margin-bottom:1.0666667em;line-height:1.3333333}.prose-lg h3{font-size:1.3333333em;margin-top:1.6666667em;margin-bottom:.6666667em;line-height:1.5}.prose-lg h4{margin-top:1.7777778em;margin-bottom:.4444444em;line-height:1.5555556}.prose-lg figure,.prose-lg img,.prose-lg video{margin-top:1.7777778em;margin-bottom:1.7777778em}.prose-lg figure>*{margin-top:0;margin-bottom:0}.prose-lg figure figcaption{font-size:.8888889em;line-height:1.5;margin-top:1em}.prose-lg code{font-size:.8888889em}.prose-lg h2 code{font-size:.8666667em}.prose-lg h3 code{font-size:.875em}.prose-lg pre{font-size:.8888889em;line-height:1.75;margin-top:2em;margin-bottom:2em;border-radius:.375rem;padding:1em 1.5em}.prose-lg ol,.prose-lg ul{margin-top:1.3333333em;margin-bottom:1.3333333em}.prose-lg li{margin-top:.6666667em;margin-bottom:.6666667em}.prose-lg ol>li{padding-left:1.6666667em}.prose-lg ol>li:before{left:0}.prose-lg ul>li{padding-left:1.6666667em}.prose-lg ul>li:before{width:.3333333em;height:.3333333em;top:.72222em;left:.2222222em}.prose-lg>ul>li p{margin-top:.8888889em;margin-bottom:.8888889em}.prose-lg>ul>li>:first-child{margin-top:1.3333333em}.prose-lg>ul>li>:last-child{margin-bottom:1.3333333em}.prose-lg>ol>li>:first-child{margin-top:1.3333333em}.prose-lg>ol>li>:last-child{margin-bottom:1.3333333em}.prose-lg ol ol,.prose-lg ol ul,.prose-lg ul ol,.prose-lg ul ul{margin-top:.8888889em;margin-bottom:.8888889em}.prose-lg hr{margin-top:3.1111111em;margin-bottom:3.1111111em}.prose-lg h2+*,.prose-lg h3+*,.prose-lg h4+*,.prose-lg hr+*{margin-top:0}.prose-lg table{font-size:.8888889em;line-height:1.5}.prose-lg thead th{padding-right:.75em;padding-bottom:.75em;padding-left:.75em}.prose-lg thead th:first-child{padding-left:0}.prose-lg thead th:last-child{padding-right:0}.prose-lg tbody td{padding:.75em}.prose-lg tbody td:first-child{padding-left:0}.prose-lg tbody td:last-child{padding-right:0}.prose-lg>:first-child{margin-top:0}.prose-lg>:last-child{margin-bottom:0}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(0.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0.25rem*var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(0.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0.5rem*var(--tw-space-y-reverse))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(0.5rem*var(--tw-space-x-reverse));margin-left:calc(0.5rem*(1 - var(--tw-space-x-reverse)))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.space-x-8>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(2rem*var(--tw-space-x-reverse));margin-left:calc(2rem*(1 - var(--tw-space-x-reverse)))}.bg-clip-border{background-clip:border-box}.bg-clip-text{-webkit-background-clip:text;background-clip:text}.bg-white{--tw-bg-opacity:1;background-color:rgba(255,255,255,var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgba(243,244,246,var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgba(229,231,235,var(--tw-bg-opacity))}.bg-gray-600{--tw-bg-opacity:1;background-color:rgba(75,85,99,var(--tw-bg-opacity))}.bg-gray-800{--tw-bg-opacity:1;background-color:rgba(31,41,55,var(--tw-bg-opacity))}.bg-red-200{--tw-bg-opacity:1;background-color:rgba(254,202,202,var(--tw-bg-opacity))}.bg-red-800{--tw-bg-opacity:1;background-color:rgba(153,27,27,var(--tw-bg-opacity))}.bg-green-200{--tw-bg-opacity:1;background-color:rgba(167,243,208,var(--tw-bg-opacity))}.bg-green-500{--tw-bg-opacity:1;background-color:rgba(16,185,129,var(--tw-bg-opacity))}.bg-green-800{--tw-bg-opacity:1;background-color:rgba(6,95,70,var(--tw-bg-opacity))}.bg-blue-800{--tw-bg-opacity:1;background-color:rgba(30,64,175,var(--tw-bg-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgba(243,244,246,var(--tw-bg-opacity))}.hover\:bg-gray-300:hover{--tw-bg-opacity:1;background-color:rgba(209,213,219,var(--tw-bg-opacity))}.hover\:bg-gray-700:hover{--tw-bg-opacity:1;background-color:rgba(55,65,81,var(--tw-bg-opacity))}.hover\:bg-red-700:hover{--tw-bg-opacity:1;background-color:rgba(185,28,28,var(--tw-bg-opacity))}.hover\:bg-yellow-300:hover{--tw-bg-opacity:1;background-color:rgba(252,211,77,var(--tw-bg-opacity))}.hover\:bg-green-700:hover{--tw-bg-opacity:1;background-color:rgba(4,120,87,var(--tw-bg-opacity))}.hover\:bg-blue-700:hover{--tw-bg-opacity:1;background-color:rgba(29,78,216,var(--tw-bg-opacity))}.hover\:bg-indigo-600:hover{--tw-bg-opacity:1;background-color:rgba(79,70,229,var(--tw-bg-opacity))}.focus\:bg-gray-100:focus{--tw-bg-opacity:1;background-color:rgba(243,244,246,var(--tw-bg-opacity))}.bg-gradient-to-r{background-image:linear-gradient(90deg,var(--tw-gradient-stops))}.from-red-400{--tw-gradient-from:#f87171;--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to,rgba(248,113,113,0))}.to-blue-500{--tw-gradient-to:#3b82f6}.bg-center{background-position:50%}.bg-no-repeat{background-repeat:no-repeat}.bg-cover{background-size:cover}.border-transparent{border-color:transparent}.border-black{--tw-border-opacity:1;border-color:rgba(0,0,0,var(--tw-border-opacity))}.border-gray-100{--tw-border-opacity:1;border-color:rgba(243,244,246,var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity:1;border-color:rgba(229,231,235,var(--tw-border-opacity))}.border-gray-300{--tw-border-opacity:1;border-color:rgba(209,213,219,var(--tw-border-opacity))}.border-gray-400{--tw-border-opacity:1;border-color:rgba(156,163,175,var(--tw-border-opacity))}.border-gray-500{--tw-border-opacity:1;border-color:rgba(107,114,128,var(--tw-border-opacity))}.border-blue-100{--tw-border-opacity:1;border-color:rgba(219,234,254,var(--tw-border-opacity))}.border-blue-200{--tw-border-opacity:1;border-color:rgba(191,219,254,var(--tw-border-opacity))}.border-indigo-400{--tw-border-opacity:1;border-color:rgba(129,140,248,var(--tw-border-opacity))}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgba(209,213,219,var(--tw-border-opacity))}.hover\:border-red-300:hover{--tw-border-opacity:1;border-color:rgba(252,165,165,var(--tw-border-opacity))}.focus\:border-gray-300:focus{--tw-border-opacity:1;border-color:rgba(209,213,219,var(--tw-border-opacity))}.focus\:border-gray-900:focus{--tw-border-opacity:1;border-color:rgba(17,24,39,var(--tw-border-opacity))}.focus\:border-red-900:focus{--tw-border-opacity:1;border-color:rgba(127,29,29,var(--tw-border-opacity))}.focus\:border-green-900:focus{--tw-border-opacity:1;border-color:rgba(6,78,59,var(--tw-border-opacity))}.focus\:border-blue-900:focus{--tw-border-opacity:1;border-color:rgba(30,58,138,var(--tw-border-opacity))}.focus\:border-indigo-300:focus{--tw-border-opacity:1;border-color:rgba(165,180,252,var(--tw-border-opacity))}.focus\:border-indigo-700:focus{--tw-border-opacity:1;border-color:rgba(67,56,202,var(--tw-border-opacity))}.rounded{border-radius:.25rem}.rounded-md{border-radius:.375rem}.rounded-full{border-radius:9999px}.border-2{border-width:2px}.border{border-width:1px}.border-b-0{border-bottom-width:0}.border-b-2{border-bottom-width:2px}.border-t-4{border-top-width:4px}.border-b-4{border-bottom-width:4px}.border-t-8{border-top-width:8px}.border-b-8{border-bottom-width:8px}.border-t{border-top-width:1px}.border-r{border-right-width:1px}.border-b{border-bottom-width:1px}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-move{cursor:move}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.inline-grid{display:inline-grid}.hidden{display:none}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.self-center{align-self:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.flex-1{flex:1 1 0%}.flex-auto{flex:1 1 auto}.flex-none{flex:none}.float-right{float:right}.font-sans{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.font-normal{font-weight:400}.font-medium{font-weight:500}.font-semibold{font-weight:600}.font-bold{font-weight:700}.font-extrabold{font-weight:800}.font-black{font-weight:900}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-10{height:2.5rem}.h-16{height:4rem}.h-20{height:5rem}.h-24{height:6rem}.h-32{height:8rem}.h-full{height:100%}.text-xs{font-size:.75rem;line-height:1rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem}.text-lg,.text-xl{line-height:1.75rem}.text-xl{font-size:1.25rem}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-5xl{font-size:3rem;line-height:1}.text-6xl{font-size:3.75rem;line-height:1}.leading-5{line-height:1.25rem}.leading-7{line-height:1.75rem}.leading-none{line-height:1}.leading-tight{line-height:1.25}.leading-snug{line-height:1.375}.leading-relaxed{line-height:1.625}.leading-loose{line-height:2}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.m-1{margin:.25rem}.m-auto{margin:auto}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-4{margin-top:1rem;margin-bottom:1rem}.mx-auto{margin-left:auto;margin-right:auto}.mt-1{margin-top:.25rem}.mr-1{margin-right:.25rem}.mb-1{margin-bottom:.25rem}.ml-1{margin-left:.25rem}.mt-2{margin-top:.5rem}.mr-2{margin-right:.5rem}.mb-2{margin-bottom:.5rem}.ml-2{margin-left:.5rem}.mt-3{margin-top:.75rem}.ml-3{margin-left:.75rem}.mt-4{margin-top:1rem}.mb-4{margin-bottom:1rem}.ml-4{margin-left:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.ml-12{margin-left:3rem}.-mt-px{margin-top:-1px}.max-w-xs{max-width:20rem}.max-w-md{max-width:28rem}.max-w-xl{max-width:36rem}.max-w-6xl{max-width:72rem}.max-w-7xl{max-width:80rem}.min-h-screen{min-height:100vh}.opacity-0{opacity:0}.opacity-5{opacity:.05}.opacity-25{opacity:.25}.opacity-100{opacity:1}.disabled\:opacity-25:disabled{opacity:.25}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.overflow-hidden{overflow:hidden}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-6{padding:1.5rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.px-1{padding-left:.25rem;padding-right:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.pt-1{padding-top:.25rem}.pt-2{padding-top:.5rem}.pl-2{padding-left:.5rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pt-6{padding-top:1.5rem}.pt-8{padding-top:2rem}.pt-12{padding-top:3rem}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.top-0{top:0}.right-0{right:0}.left-0{left:0}*{--tw-shadow:0 0 transparent}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,0.05)}.shadow,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 transparent),var(--tw-ring-shadow,0 0 transparent),var(--tw-shadow)}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,0.1),0 1px 2px 0 rgba(0,0,0,0.06)}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,0.1),0 2px 4px -1px rgba(0,0,0,0.06)}.shadow-lg,.shadow-md{box-shadow:var(--tw-ring-offset-shadow,0 0 transparent),var(--tw-ring-shadow,0 0 transparent),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,0.1),0 4px 6px -2px rgba(0,0,0,0.05)}*{--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,0.5);--tw-ring-offset-shadow:0 0 transparent;--tw-ring-shadow:0 0 transparent}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring:focus,.ring-1{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 transparent)}.focus\:ring:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-black{--tw-ring-opacity:1;--tw-ring-color:rgba(0,0,0,var(--tw-ring-opacity))}.ring-gray-300{--tw-ring-opacity:1;--tw-ring-color:rgba(209,213,219,var(--tw-ring-opacity))}.ring-red-300{--tw-ring-opacity:1;--tw-ring-color:rgba(252,165,165,var(--tw-ring-opacity))}.ring-green-300{--tw-ring-opacity:1;--tw-ring-color:rgba(110,231,183,var(--tw-ring-opacity))}.ring-green-600{--tw-ring-opacity:1;--tw-ring-color:rgba(5,150,105,var(--tw-ring-opacity))}.ring-blue-300{--tw-ring-opacity:1;--tw-ring-color:rgba(147,197,253,var(--tw-ring-opacity))}.focus\:ring-indigo-200:focus{--tw-ring-opacity:1;--tw-ring-color:rgba(199,210,254,var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity:0.05}.focus\:ring-opacity-50:focus{--tw-ring-opacity:0.5}.fill-current{fill:currentColor}.text-center{text-align:center}.text-right{text-align:right}.text-transparent{color:transparent}.text-white{--tw-text-opacity:1;color:rgba(255,255,255,var(--tw-text-opacity))}.text-gray-200{--tw-text-opacity:1;color:rgba(229,231,235,var(--tw-text-opacity))}.text-gray-300{--tw-text-opacity:1;color:rgba(209,213,219,var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgba(156,163,175,var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgba(107,114,128,var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgba(75,85,99,var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgba(55,65,81,var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgba(31,41,55,var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgba(17,24,39,var(--tw-text-opacity))}.text-red-100{--tw-text-opacity:1;color:rgba(254,226,226,var(--tw-text-opacity))}.text-red-500{--tw-text-opacity:1;color:rgba(239,68,68,var(--tw-text-opacity))}.text-red-600{--tw-text-opacity:1;color:rgba(220,38,38,var(--tw-text-opacity))}.text-red-800{--tw-text-opacity:1;color:rgba(153,27,27,var(--tw-text-opacity))}.text-yellow-500{--tw-text-opacity:1;color:rgba(245,158,11,var(--tw-text-opacity))}.text-green-500{--tw-text-opacity:1;color:rgba(16,185,129,var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgba(5,150,105,var(--tw-text-opacity))}.text-indigo-600{--tw-text-opacity:1;color:rgba(79,70,229,var(--tw-text-opacity))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgba(255,255,255,var(--tw-text-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgba(75,85,99,var(--tw-text-opacity))}.hover\:text-gray-700:hover{--tw-text-opacity:1;color:rgba(55,65,81,var(--tw-text-opacity))}.hover\:text-gray-900:hover{--tw-text-opacity:1;color:rgba(17,24,39,var(--tw-text-opacity))}.hover\:text-red-700:hover{--tw-text-opacity:1;color:rgba(185,28,28,var(--tw-text-opacity))}.hover\:text-green-700:hover{--tw-text-opacity:1;color:rgba(4,120,87,var(--tw-text-opacity))}.focus\:text-gray-700:focus{--tw-text-opacity:1;color:rgba(55,65,81,var(--tw-text-opacity))}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.italic{font-style:italic}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.underline{text-decoration:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.subpixel-antialiased{-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto}.tracking-wider{letter-spacing:.05em}.tracking-widest{letter-spacing:.1em}.align-top{vertical-align:top}.align-middle{vertical-align:middle}.break-all{word-break:break-all}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-10{width:2.5rem}.w-20{width:5rem}.w-24{width:6rem}.w-48{width:12rem}.w-72{width:18rem}.w-auto{width:auto}.w-full{width:100%}.z-40{z-index:40}.z-50{z-index:50}.gap-4{gap:1rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-1{row-gap:.25rem}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.transform{--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;transform:translateX(var(--tw-translate-x)) translateY(var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.origin-top{transform-origin:top}.origin-top-right{transform-origin:top right}.origin-top-left{transform-origin:top left}.scale-95{--tw-scale-x:.95;--tw-scale-y:.95}.scale-100{--tw-scale-x:1;--tw-scale-y:1}.transition{transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-75{transition-duration:75ms}.duration-150{transition-duration:.15s}.duration-200{transition-duration:.2s}@-webkit-keyframes spin{to{transform:rotate(1turn)}}@keyframes spin{to{transform:rotate(1turn)}}@-webkit-keyframes ping{75%,to{transform:scale(2);opacity:0}}@keyframes ping{75%,to{transform:scale(2);opacity:0}}@-webkit-keyframes pulse{50%{opacity:.5}}@keyframes pulse{50%{opacity:.5}}@-webkit-keyframes bounce{0%,to{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:none;-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}@keyframes bounce{0%,to{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:none;-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}.animate-spin{-webkit-animation:spin 1s linear infinite;animation:spin 1s linear infinite}[x-cloak]{display:none!important}@media (min-width:640px){.sm\:space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(0px*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0px*var(--tw-space-y-reverse))}.sm\:space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.sm\:rounded-lg{border-radius:.5rem}.sm\:block{display:block}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:justify-start{justify-content:flex-start}.sm\:justify-center{justify-content:center}.sm\:h-48{height:12rem}.sm\:ml-6{margin-left:1.5rem}.sm\:max-w-md{max-width:28rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:pt-0{padding-top:0}.sm\:w-auto{width:auto}.sm\:w-3\/5{width:60%}.sm\:w-5\/12{width:41.666667%}}@media (min-width:768px){.md\:space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(0px*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0px*var(--tw-space-y-reverse))}.md\:space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.md\:hidden{display:none}.md\:flex-row{flex-direction:row}.md\:h-64{height:16rem}.md\:text-9xl{font-size:8rem;line-height:1}.md\:w-72{width:18rem}.md\:w-auto{width:auto}.md\:w-1\/2{width:50%}.md\:w-2\/3{width:66.666667%}.md\:w-1\/4{width:25%}.md\:w-3\/4{width:75%}.md\:w-5\/6{width:83.333333%}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:1024px){.lg\:h-96{height:24rem}.lg\:px-8{padding-left:2rem;padding-right:2rem}.lg\:w-1\/2{width:50%}.lg\:w-1\/3{width:33.333333%}.lg\:w-3\/4{width:75%}.lg\:w-4\/12{width:33.333333%}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}} +/*! modern-normalize v1.0.0 | MIT License | https://github.com/sindresorhus/modern-normalize */:root{-moz-tab-size:4;-o-tab-size:4;tab-size:4}html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji}hr{height:0;color:inherit}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}[type=button],[type=submit],button{-webkit-appearance:button}legend{padding:0}progress{vertical-align:baseline}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}button{background-color:transparent;background-image:none}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}fieldset,ol,ul{margin:0;padding:0}ol,ul{list-style:none}html{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}body{font-family:inherit;line-height:inherit}*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}hr{border-top-width:1px}img{border-style:solid}textarea{resize:vertical}input::-moz-placeholder, textarea::-moz-placeholder{color:#9ca3af}input:-ms-input-placeholder, textarea:-ms-input-placeholder{color:#9ca3af}input::placeholder,textarea::placeholder{color:#9ca3af}button{cursor:pointer}table{border-collapse:collapse}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}button,input,optgroup,select,textarea{padding:0;line-height:inherit;color:inherit}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[type=date],[type=number],[type=password],[type=search],[type=text],[type=time],[type=url],select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem}[type=date]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,select:focus,textarea:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 transparent);border-color:#2563eb}input::-moz-placeholder, textarea::-moz-placeholder{color:#6b7280;opacity:1}input:-ms-input-placeholder, textarea:-ms-input-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}select{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;color-adjust:exact}[type=checkbox]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0}[type=checkbox]:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 transparent)}[type=checkbox]:checked{background-size:100% 100%;background-position:50%;background-repeat:no-repeat;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3E%3C/svg%3E")}[type=checkbox]:checked,[type=checkbox]:checked:focus,[type=checkbox]:checked:hover{border-color:transparent;background-color:currentColor}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3E%3C/svg%3E");border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:50%;background-repeat:no-repeat}[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{border-color:transparent;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px auto -webkit-focus-ring-color}.prose{color:#374151;max-width:65ch}.prose [class~=lead]{color:#4b5563;font-size:1.25em;line-height:1.6;margin-top:1.2em;margin-bottom:1.2em}.prose a{color:#111827;text-decoration:underline;font-weight:500}.prose strong{color:#111827;font-weight:600}.prose ol[type=a]{--list-counter-style:lower-alpha}.prose ol[type=i]{--list-counter-style:lower-roman}.prose ol[type="1"]{--list-counter-style:decimal}.prose ol>li{position:relative;padding-left:1.75em}.prose ol>li:before{content:counter(list-item,var(--list-counter-style,decimal)) ".";position:absolute;font-weight:400;color:#6b7280;left:0}.prose ul>li{position:relative;padding-left:1.75em}.prose ul>li:before{content:"";position:absolute;background-color:#d1d5db;border-radius:50%;width:.375em;height:.375em;top:.6875em;left:.25em}.prose hr{border-color:#e5e7eb;border-top-width:1px;margin-top:3em;margin-bottom:3em}.prose blockquote{font-weight:500;font-style:italic;color:#111827;border-left-width:.25rem;border-left-color:#e5e7eb;quotes:"\201C""\201D""\2018""\2019";margin-top:1.6em;margin-bottom:1.6em;padding-left:1em}.prose blockquote p:first-of-type:before{content:open-quote}.prose blockquote p:last-of-type:after{content:close-quote}.prose h1{color:#111827;font-weight:800;font-size:2.25em;margin-top:0;margin-bottom:.8888889em;line-height:1.1111111}.prose h2{color:#111827;font-weight:700;font-size:1.5em;margin-top:2em;margin-bottom:1em;line-height:1.3333333}.prose h3{font-size:1.25em;margin-top:1.6em;margin-bottom:.6em;line-height:1.6}.prose h3,.prose h4{color:#111827;font-weight:600}.prose h4{margin-top:1.5em;margin-bottom:.5em;line-height:1.5}.prose figure figcaption{color:#6b7280;font-size:.875em;line-height:1.4285714;margin-top:.8571429em}.prose code{color:#111827;font-weight:600;font-size:.875em}.prose code:after,.prose code:before{content:"`"}.prose a code{color:#111827}.prose pre{color:#e5e7eb;background-color:#1f2937;overflow-x:auto;font-size:.875em;line-height:1.7142857;margin-top:1.7142857em;margin-bottom:1.7142857em;border-radius:.375rem;padding:.8571429em 1.1428571em}.prose pre code{background-color:transparent;border-width:0;border-radius:0;padding:0;font-weight:400;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}.prose pre code:after,.prose pre code:before{content:none}.prose table{width:100%;table-layout:auto;text-align:left;margin-top:2em;margin-bottom:2em;font-size:.875em;line-height:1.7142857}.prose thead{color:#111827;font-weight:600;border-bottom-width:1px;border-bottom-color:#d1d5db}.prose thead th{vertical-align:bottom;padding-right:.5714286em;padding-bottom:.5714286em;padding-left:.5714286em}.prose tbody tr{border-bottom-width:1px;border-bottom-color:#e5e7eb}.prose tbody tr:last-child{border-bottom-width:0}.prose tbody td{vertical-align:top;padding:.5714286em}.prose{font-size:1rem;line-height:1.75}.prose p{margin-top:1.25em;margin-bottom:1.25em}.prose figure,.prose img,.prose video{margin-top:2em;margin-bottom:2em}.prose figure>*{margin-top:0;margin-bottom:0}.prose h2 code{font-size:.875em}.prose h3 code{font-size:.9em}.prose ol,.prose ul{margin-top:1.25em;margin-bottom:1.25em}.prose li{margin-top:.5em;margin-bottom:.5em}.prose>ul>li p{margin-top:.75em;margin-bottom:.75em}.prose>ul>li>:first-child{margin-top:1.25em}.prose>ul>li>:last-child{margin-bottom:1.25em}.prose>ol>li>:first-child{margin-top:1.25em}.prose>ol>li>:last-child{margin-bottom:1.25em}.prose ol ol,.prose ol ul,.prose ul ol,.prose ul ul{margin-top:.75em;margin-bottom:.75em}.prose h2+*,.prose h3+*,.prose h4+*,.prose hr+*{margin-top:0}.prose thead th:first-child{padding-left:0}.prose thead th:last-child{padding-right:0}.prose tbody td:first-child{padding-left:0}.prose tbody td:last-child{padding-right:0}.prose>:first-child{margin-top:0}.prose>:last-child{margin-bottom:0}.prose-lg{font-size:1.125rem;line-height:1.7777778}.prose-lg p{margin-top:1.3333333em;margin-bottom:1.3333333em}.prose-lg [class~=lead]{font-size:1.2222222em;line-height:1.4545455;margin-top:1.0909091em;margin-bottom:1.0909091em}.prose-lg blockquote{margin-top:1.6666667em;margin-bottom:1.6666667em;padding-left:1em}.prose-lg h1{font-size:2.6666667em;margin-top:0;margin-bottom:.8333333em;line-height:1}.prose-lg h2{font-size:1.6666667em;margin-top:1.8666667em;margin-bottom:1.0666667em;line-height:1.3333333}.prose-lg h3{font-size:1.3333333em;margin-top:1.6666667em;margin-bottom:.6666667em;line-height:1.5}.prose-lg h4{margin-top:1.7777778em;margin-bottom:.4444444em;line-height:1.5555556}.prose-lg figure,.prose-lg img,.prose-lg video{margin-top:1.7777778em;margin-bottom:1.7777778em}.prose-lg figure>*{margin-top:0;margin-bottom:0}.prose-lg figure figcaption{font-size:.8888889em;line-height:1.5;margin-top:1em}.prose-lg code{font-size:.8888889em}.prose-lg h2 code{font-size:.8666667em}.prose-lg h3 code{font-size:.875em}.prose-lg pre{font-size:.8888889em;line-height:1.75;margin-top:2em;margin-bottom:2em;border-radius:.375rem;padding:1em 1.5em}.prose-lg ol,.prose-lg ul{margin-top:1.3333333em;margin-bottom:1.3333333em}.prose-lg li{margin-top:.6666667em;margin-bottom:.6666667em}.prose-lg ol>li{padding-left:1.6666667em}.prose-lg ol>li:before{left:0}.prose-lg ul>li{padding-left:1.6666667em}.prose-lg ul>li:before{width:.3333333em;height:.3333333em;top:.72222em;left:.2222222em}.prose-lg>ul>li p{margin-top:.8888889em;margin-bottom:.8888889em}.prose-lg>ul>li>:first-child{margin-top:1.3333333em}.prose-lg>ul>li>:last-child{margin-bottom:1.3333333em}.prose-lg>ol>li>:first-child{margin-top:1.3333333em}.prose-lg>ol>li>:last-child{margin-bottom:1.3333333em}.prose-lg ol ol,.prose-lg ol ul,.prose-lg ul ol,.prose-lg ul ul{margin-top:.8888889em;margin-bottom:.8888889em}.prose-lg hr{margin-top:3.1111111em;margin-bottom:3.1111111em}.prose-lg h2+*,.prose-lg h3+*,.prose-lg h4+*,.prose-lg hr+*{margin-top:0}.prose-lg table{font-size:.8888889em;line-height:1.5}.prose-lg thead th{padding-right:.75em;padding-bottom:.75em;padding-left:.75em}.prose-lg thead th:first-child{padding-left:0}.prose-lg thead th:last-child{padding-right:0}.prose-lg tbody td{padding:.75em}.prose-lg tbody td:first-child{padding-left:0}.prose-lg tbody td:last-child{padding-right:0}.prose-lg>:first-child{margin-top:0}.prose-lg>:last-child{margin-bottom:0}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(0.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0.25rem*var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(0.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0.5rem*var(--tw-space-y-reverse))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(0.5rem*var(--tw-space-x-reverse));margin-left:calc(0.5rem*(1 - var(--tw-space-x-reverse)))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.space-x-8>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(2rem*var(--tw-space-x-reverse));margin-left:calc(2rem*(1 - var(--tw-space-x-reverse)))}.bg-clip-border{background-clip:border-box}.bg-clip-text{-webkit-background-clip:text;background-clip:text}.bg-white{--tw-bg-opacity:1;background-color:rgba(255,255,255,var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgba(243,244,246,var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgba(229,231,235,var(--tw-bg-opacity))}.bg-gray-600{--tw-bg-opacity:1;background-color:rgba(75,85,99,var(--tw-bg-opacity))}.bg-gray-800{--tw-bg-opacity:1;background-color:rgba(31,41,55,var(--tw-bg-opacity))}.bg-red-200{--tw-bg-opacity:1;background-color:rgba(254,202,202,var(--tw-bg-opacity))}.bg-red-800{--tw-bg-opacity:1;background-color:rgba(153,27,27,var(--tw-bg-opacity))}.bg-green-200{--tw-bg-opacity:1;background-color:rgba(167,243,208,var(--tw-bg-opacity))}.bg-green-500{--tw-bg-opacity:1;background-color:rgba(16,185,129,var(--tw-bg-opacity))}.bg-green-800{--tw-bg-opacity:1;background-color:rgba(6,95,70,var(--tw-bg-opacity))}.bg-blue-800{--tw-bg-opacity:1;background-color:rgba(30,64,175,var(--tw-bg-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgba(243,244,246,var(--tw-bg-opacity))}.hover\:bg-gray-300:hover{--tw-bg-opacity:1;background-color:rgba(209,213,219,var(--tw-bg-opacity))}.hover\:bg-gray-700:hover{--tw-bg-opacity:1;background-color:rgba(55,65,81,var(--tw-bg-opacity))}.hover\:bg-red-700:hover{--tw-bg-opacity:1;background-color:rgba(185,28,28,var(--tw-bg-opacity))}.hover\:bg-yellow-300:hover{--tw-bg-opacity:1;background-color:rgba(252,211,77,var(--tw-bg-opacity))}.hover\:bg-green-700:hover{--tw-bg-opacity:1;background-color:rgba(4,120,87,var(--tw-bg-opacity))}.hover\:bg-blue-700:hover{--tw-bg-opacity:1;background-color:rgba(29,78,216,var(--tw-bg-opacity))}.hover\:bg-indigo-600:hover{--tw-bg-opacity:1;background-color:rgba(79,70,229,var(--tw-bg-opacity))}.focus\:bg-gray-100:focus{--tw-bg-opacity:1;background-color:rgba(243,244,246,var(--tw-bg-opacity))}.bg-gradient-to-r{background-image:linear-gradient(90deg,var(--tw-gradient-stops))}.from-red-400{--tw-gradient-from:#f87171;--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to,rgba(248,113,113,0))}.to-blue-500{--tw-gradient-to:#3b82f6}.bg-center{background-position:50%}.bg-no-repeat{background-repeat:no-repeat}.bg-cover{background-size:cover}.border-transparent{border-color:transparent}.border-black{--tw-border-opacity:1;border-color:rgba(0,0,0,var(--tw-border-opacity))}.border-gray-100{--tw-border-opacity:1;border-color:rgba(243,244,246,var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity:1;border-color:rgba(229,231,235,var(--tw-border-opacity))}.border-gray-300{--tw-border-opacity:1;border-color:rgba(209,213,219,var(--tw-border-opacity))}.border-gray-400{--tw-border-opacity:1;border-color:rgba(156,163,175,var(--tw-border-opacity))}.border-gray-500{--tw-border-opacity:1;border-color:rgba(107,114,128,var(--tw-border-opacity))}.border-blue-100{--tw-border-opacity:1;border-color:rgba(219,234,254,var(--tw-border-opacity))}.border-blue-200{--tw-border-opacity:1;border-color:rgba(191,219,254,var(--tw-border-opacity))}.border-indigo-400{--tw-border-opacity:1;border-color:rgba(129,140,248,var(--tw-border-opacity))}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgba(209,213,219,var(--tw-border-opacity))}.hover\:border-red-300:hover{--tw-border-opacity:1;border-color:rgba(252,165,165,var(--tw-border-opacity))}.focus\:border-gray-300:focus{--tw-border-opacity:1;border-color:rgba(209,213,219,var(--tw-border-opacity))}.focus\:border-gray-900:focus{--tw-border-opacity:1;border-color:rgba(17,24,39,var(--tw-border-opacity))}.focus\:border-red-900:focus{--tw-border-opacity:1;border-color:rgba(127,29,29,var(--tw-border-opacity))}.focus\:border-green-900:focus{--tw-border-opacity:1;border-color:rgba(6,78,59,var(--tw-border-opacity))}.focus\:border-blue-900:focus{--tw-border-opacity:1;border-color:rgba(30,58,138,var(--tw-border-opacity))}.focus\:border-indigo-300:focus{--tw-border-opacity:1;border-color:rgba(165,180,252,var(--tw-border-opacity))}.focus\:border-indigo-700:focus{--tw-border-opacity:1;border-color:rgba(67,56,202,var(--tw-border-opacity))}.rounded{border-radius:.25rem}.rounded-md{border-radius:.375rem}.rounded-full{border-radius:9999px}.border-0{border-width:0}.border-2{border-width:2px}.border{border-width:1px}.border-b-0{border-bottom-width:0}.border-b-2{border-bottom-width:2px}.border-t-4{border-top-width:4px}.border-b-4{border-bottom-width:4px}.border-t-8{border-top-width:8px}.border-b-8{border-bottom-width:8px}.border-t{border-top-width:1px}.border-r{border-right-width:1px}.border-b{border-bottom-width:1px}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-move{cursor:move}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.inline-grid{display:inline-grid}.hidden{display:none}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.self-center{align-self:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.flex-1{flex:1 1 0%}.flex-auto{flex:1 1 auto}.flex-none{flex:none}.float-right{float:right}.font-sans{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.font-normal{font-weight:400}.font-medium{font-weight:500}.font-semibold{font-weight:600}.font-bold{font-weight:700}.font-extrabold{font-weight:800}.font-black{font-weight:900}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-10{height:2.5rem}.h-16{height:4rem}.h-20{height:5rem}.h-24{height:6rem}.h-32{height:8rem}.h-full{height:100%}.text-xs{font-size:.75rem;line-height:1rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem}.text-lg,.text-xl{line-height:1.75rem}.text-xl{font-size:1.25rem}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-5xl{font-size:3rem;line-height:1}.text-6xl{font-size:3.75rem;line-height:1}.leading-5{line-height:1.25rem}.leading-7{line-height:1.75rem}.leading-none{line-height:1}.leading-tight{line-height:1.25}.leading-snug{line-height:1.375}.leading-relaxed{line-height:1.625}.leading-loose{line-height:2}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.m-1{margin:.25rem}.m-auto{margin:auto}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-4{margin-top:1rem;margin-bottom:1rem}.mx-auto{margin-left:auto;margin-right:auto}.mt-1{margin-top:.25rem}.mr-1{margin-right:.25rem}.mb-1{margin-bottom:.25rem}.ml-1{margin-left:.25rem}.mt-2{margin-top:.5rem}.mr-2{margin-right:.5rem}.mb-2{margin-bottom:.5rem}.ml-2{margin-left:.5rem}.mt-3{margin-top:.75rem}.ml-3{margin-left:.75rem}.mt-4{margin-top:1rem}.mb-4{margin-bottom:1rem}.ml-4{margin-left:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.ml-12{margin-left:3rem}.-mt-px{margin-top:-1px}.max-w-xs{max-width:20rem}.max-w-md{max-width:28rem}.max-w-xl{max-width:36rem}.max-w-3xl{max-width:48rem}.max-w-6xl{max-width:72rem}.max-w-7xl{max-width:80rem}.min-h-screen{min-height:100vh}.opacity-0{opacity:0}.opacity-5{opacity:.05}.opacity-25{opacity:.25}.opacity-100{opacity:1}.disabled\:opacity-25:disabled{opacity:.25}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.overflow-hidden{overflow:hidden}.p-0{padding:0}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-6{padding:1.5rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.px-1{padding-left:.25rem;padding-right:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.pt-1{padding-top:.25rem}.pt-2{padding-top:.5rem}.pl-2{padding-left:.5rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pt-6{padding-top:1.5rem}.pt-8{padding-top:2rem}.pt-12{padding-top:3rem}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.top-0{top:0}.right-0{right:0}.left-0{left:0}*{--tw-shadow:0 0 transparent}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,0.05)}.shadow,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 transparent),var(--tw-ring-shadow,0 0 transparent),var(--tw-shadow)}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,0.1),0 1px 2px 0 rgba(0,0,0,0.06)}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,0.1),0 2px 4px -1px rgba(0,0,0,0.06)}.shadow-lg,.shadow-md{box-shadow:var(--tw-ring-offset-shadow,0 0 transparent),var(--tw-ring-shadow,0 0 transparent),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,0.1),0 4px 6px -2px rgba(0,0,0,0.05)}.shadow-none{--tw-shadow:0 0 transparent;box-shadow:var(--tw-ring-offset-shadow,0 0 transparent),var(--tw-ring-shadow,0 0 transparent),var(--tw-shadow)}*{--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,0.5);--tw-ring-offset-shadow:0 0 transparent;--tw-ring-shadow:0 0 transparent}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring:focus,.ring-1{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 transparent)}.focus\:ring:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-black{--tw-ring-opacity:1;--tw-ring-color:rgba(0,0,0,var(--tw-ring-opacity))}.ring-gray-300{--tw-ring-opacity:1;--tw-ring-color:rgba(209,213,219,var(--tw-ring-opacity))}.ring-red-300{--tw-ring-opacity:1;--tw-ring-color:rgba(252,165,165,var(--tw-ring-opacity))}.ring-green-300{--tw-ring-opacity:1;--tw-ring-color:rgba(110,231,183,var(--tw-ring-opacity))}.ring-green-600{--tw-ring-opacity:1;--tw-ring-color:rgba(5,150,105,var(--tw-ring-opacity))}.ring-blue-300{--tw-ring-opacity:1;--tw-ring-color:rgba(147,197,253,var(--tw-ring-opacity))}.focus\:ring-indigo-200:focus{--tw-ring-opacity:1;--tw-ring-color:rgba(199,210,254,var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity:0.05}.focus\:ring-opacity-50:focus{--tw-ring-opacity:0.5}.fill-current{fill:currentColor}.text-center{text-align:center}.text-right{text-align:right}.text-transparent{color:transparent}.text-white{--tw-text-opacity:1;color:rgba(255,255,255,var(--tw-text-opacity))}.text-gray-200{--tw-text-opacity:1;color:rgba(229,231,235,var(--tw-text-opacity))}.text-gray-300{--tw-text-opacity:1;color:rgba(209,213,219,var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgba(156,163,175,var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgba(107,114,128,var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgba(75,85,99,var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgba(55,65,81,var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgba(31,41,55,var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgba(17,24,39,var(--tw-text-opacity))}.text-red-100{--tw-text-opacity:1;color:rgba(254,226,226,var(--tw-text-opacity))}.text-red-500{--tw-text-opacity:1;color:rgba(239,68,68,var(--tw-text-opacity))}.text-red-600{--tw-text-opacity:1;color:rgba(220,38,38,var(--tw-text-opacity))}.text-red-800{--tw-text-opacity:1;color:rgba(153,27,27,var(--tw-text-opacity))}.text-yellow-500{--tw-text-opacity:1;color:rgba(245,158,11,var(--tw-text-opacity))}.text-green-500{--tw-text-opacity:1;color:rgba(16,185,129,var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgba(5,150,105,var(--tw-text-opacity))}.text-indigo-600{--tw-text-opacity:1;color:rgba(79,70,229,var(--tw-text-opacity))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgba(255,255,255,var(--tw-text-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgba(75,85,99,var(--tw-text-opacity))}.hover\:text-gray-700:hover{--tw-text-opacity:1;color:rgba(55,65,81,var(--tw-text-opacity))}.hover\:text-gray-900:hover{--tw-text-opacity:1;color:rgba(17,24,39,var(--tw-text-opacity))}.hover\:text-red-700:hover{--tw-text-opacity:1;color:rgba(185,28,28,var(--tw-text-opacity))}.hover\:text-green-700:hover{--tw-text-opacity:1;color:rgba(4,120,87,var(--tw-text-opacity))}.focus\:text-gray-700:focus{--tw-text-opacity:1;color:rgba(55,65,81,var(--tw-text-opacity))}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.italic{font-style:italic}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.underline{text-decoration:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.subpixel-antialiased{-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto}.tracking-wider{letter-spacing:.05em}.tracking-widest{letter-spacing:.1em}.align-top{vertical-align:top}.align-middle{vertical-align:middle}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-10{width:2.5rem}.w-20{width:5rem}.w-24{width:6rem}.w-48{width:12rem}.w-72{width:18rem}.w-auto{width:auto}.w-full{width:100%}.z-40{z-index:40}.z-50{z-index:50}.gap-4{gap:1rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-1{row-gap:.25rem}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.transform{--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;transform:translateX(var(--tw-translate-x)) translateY(var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.origin-top{transform-origin:top}.origin-top-right{transform-origin:top right}.origin-top-left{transform-origin:top left}.scale-95{--tw-scale-x:.95;--tw-scale-y:.95}.scale-100{--tw-scale-x:1;--tw-scale-y:1}.transition{transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-75{transition-duration:75ms}.duration-150{transition-duration:.15s}.duration-200{transition-duration:.2s}@-webkit-keyframes spin{to{transform:rotate(1turn)}}@keyframes spin{to{transform:rotate(1turn)}}@-webkit-keyframes ping{75%,to{transform:scale(2);opacity:0}}@keyframes ping{75%,to{transform:scale(2);opacity:0}}@-webkit-keyframes pulse{50%{opacity:.5}}@keyframes pulse{50%{opacity:.5}}@-webkit-keyframes bounce{0%,to{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:none;-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}@keyframes bounce{0%,to{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:none;-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}.animate-spin{-webkit-animation:spin 1s linear infinite;animation:spin 1s linear infinite}[x-cloak]{display:none!important}@media (min-width:640px){.sm\:space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(0px*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0px*var(--tw-space-y-reverse))}.sm\:space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.sm\:rounded-lg{border-radius:.5rem}.sm\:block{display:block}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:justify-start{justify-content:flex-start}.sm\:justify-center{justify-content:center}.sm\:h-48{height:12rem}.sm\:mt-0{margin-top:0}.sm\:ml-6{margin-left:1.5rem}.sm\:max-w-md{max-width:28rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:pt-0{padding-top:0}.sm\:w-auto{width:auto}.sm\:w-3\/5{width:60%}.sm\:w-5\/12{width:41.666667%}}@media (min-width:768px){.md\:space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(0px*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0px*var(--tw-space-y-reverse))}.md\:space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.md\:hidden{display:none}.md\:flex-row{flex-direction:row}.md\:h-64{height:16rem}.md\:text-9xl{font-size:8rem;line-height:1}.md\:w-72{width:18rem}.md\:w-auto{width:auto}.md\:w-2\/3{width:66.666667%}.md\:w-1\/4{width:25%}.md\:w-3\/4{width:75%}.md\:w-5\/6{width:83.333333%}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:1024px){.lg\:h-96{height:24rem}.lg\:px-8{padding-left:2rem;padding-right:2rem}.lg\:w-1\/2{width:50%}.lg\:w-3\/4{width:75%}.lg\:w-4\/12{width:33.333333%}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}} diff --git a/resources/views/components/button-link/base.blade.php b/resources/views/components/button-link/base.blade.php new file mode 100644 index 0000000..2deae80 --- /dev/null +++ b/resources/views/components/button-link/base.blade.php @@ -0,0 +1,3 @@ +merge(['class' => "px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white text-center uppercase tracking-widest hover:bg-gray-700 active:bg-gray-900 focus:outline-none focus:border-gray-900 focus:ring ring-gray-300 disabled:opacity-25 transition ease-in-out duration-150"]) }}> + {{ $slot }} + diff --git a/resources/views/components/button-link/green.blade.php b/resources/views/components/button-link/green.blade.php new file mode 100644 index 0000000..defa459 --- /dev/null +++ b/resources/views/components/button-link/green.blade.php @@ -0,0 +1,3 @@ + + {{ $slot }} + diff --git a/resources/views/components/button-link/red.blade.php b/resources/views/components/button-link/red.blade.php new file mode 100644 index 0000000..9142f36 --- /dev/null +++ b/resources/views/components/button-link/red.blade.php @@ -0,0 +1,3 @@ + + {{ $slot }} + diff --git a/resources/views/foods/index.blade.php b/resources/views/foods/index.blade.php index 5963563..6fadee1 100644 --- a/resources/views/foods/index.blade.php +++ b/resources/views/foods/index.blade.php @@ -3,12 +3,9 @@

Foods

- - - - - New Food - + + Add Food +
diff --git a/resources/views/foods/show.blade.php b/resources/views/foods/show.blade.php index 9901a32..45b1c54 100644 --- a/resources/views/foods/show.blade.php +++ b/resources/views/foods/show.blade.php @@ -4,82 +4,46 @@

{{ $food->name }}@if($food->detail), {{ $food->detail }}@endif - @if($food->brand) -
{{ $food->brand }}
- @endif
- - - - - - - - - - -

-
-
-

Nutrition Facts

-
-

Serving size

-
- {{ $food->servingSizeFormatted }} - {{ $food->servingUnitFormatted ?? $food->name }} - ({{ $food->serving_weight }}g) -
-
-

Amount per serving

-
-

Calories

-
{{ $food->calories }}
-
-
-
-
-

Total Fat

-
{{ $food->fat }}g
-
-
-
-

Cholesterol

-
{{ $food->cholesterol }}mg
-
-
-
-

Sodium

-
{{ $food->sodium }}mg
-
-
-
-

Total Carbohydrate

-
{{ $food->carbohydrates }}g
-
-
-
-

Protein

-
{{ $food->protein }}g
-
-
-
-
- @if(!$food->tags->isEmpty()) -

Tags

-
- @foreach ($food->tags as $tag) - {{ $tag->name }} - @endforeach -
- @endif - @if($food->description) -

Description

-

{{ $food->description }}

- @endif +
+
+
+ @if($food->brand) +

Brand

+
+ {{ $food->brand }} +
+ @endif + @if($food->notes) +

Notes

+
+ {{ $food->notes }} +
+ @endif + @if(!$food->tags->isEmpty()) +

Tags

+
+ @foreach ($food->tags as $tag) + {{ $tag->name }} + @endforeach +
+ @endif + @if($food->description) +

Description

+

{{ $food->description }}

+ @endif + @if($food->source) +

Source

+

+ @if(filter_var($food->source, FILTER_VALIDATE_URL)) + {{ $food->source }} + @else + {{ $food->source }} + @endif +

+ @endif @if(!$food->ingredientAmountRelationships->isEmpty())

Recipes

    @@ -89,16 +53,61 @@ @endforeach
@endif - @if($food->source) -

Source

-

- @if(filter_var($food->source, FILTER_VALIDATE_URL)) - {{ $food->source }} - @else - {{ $food->source }} - @endif -

- @endif -
-
+ + + + diff --git a/resources/views/goals/index.blade.php b/resources/views/goals/index.blade.php index f2e15f1..b450361 100644 --- a/resources/views/goals/index.blade.php +++ b/resources/views/goals/index.blade.php @@ -13,7 +13,16 @@ -
{{ $date->format('D, j M Y') }}
+
+
+ + +
- - - - + Add Goal - +
diff --git a/resources/views/journal-entries/create-from-nutrients.blade.php b/resources/views/journal-entries/create-from-nutrients.blade.php index bc64301..6769e18 100644 --- a/resources/views/journal-entries/create-from-nutrients.blade.php +++ b/resources/views/journal-entries/create-from-nutrients.blade.php @@ -3,9 +3,9 @@

Add Entry

- - Add by Recipes/Food - + + Add by Recipes/Foods +
diff --git a/resources/views/journal-entries/create.blade.php b/resources/views/journal-entries/create.blade.php index c4b035c..a3b22dd 100644 --- a/resources/views/journal-entries/create.blade.php +++ b/resources/views/journal-entries/create.blade.php @@ -3,9 +3,9 @@

Add Entries

- + Add by Nutrients - +
@@ -17,14 +17,14 @@ - +
- +
Add entries diff --git a/resources/views/journal-entries/index.blade.php b/resources/views/journal-entries/index.blade.php index 540913f..8acf327 100644 --- a/resources/views/journal-entries/index.blade.php +++ b/resources/views/journal-entries/index.blade.php @@ -13,7 +13,16 @@
-
{{ $date->format('D, j M Y') }}
+
+ + + +
- - - - - New Entry - + + Add Entry +
@@ -104,7 +110,7 @@
@foreach(\App\Support\Nutrients::all()->sortBy('weight') as $nutrient) - {{ round($entries->where('meal', $meal)->sum($nutrient['value']), 2) }}{{ $nutrient['unit'] }} + {{ \App\Support\Nutrients::round($entries->where('meal', $meal)->sum($nutrient['value']), $nutrient['value']) }}{{ $nutrient['unit'] }} {{ $nutrient['value'] }}@if(!$loop->last), @endif @endforeach @@ -124,7 +130,7 @@
nutrients: @foreach(\App\Support\Nutrients::all()->sortBy('weight') as $nutrient) - {{ round($entry->{$nutrient['value']}, 2) }}{{ $nutrient['unit'] }} + {{ \App\Support\Nutrients::round($entry->{$nutrient['value']}, $nutrient['value']) }}{{ $nutrient['unit'] }} {{ $nutrient['value'] }}@if(!$loop->last), @endif @endforeach
diff --git a/resources/views/journal-entries/partials/entry-item-input.blade.php b/resources/views/journal-entries/partials/entry-item-input.blade.php index bc120ee..ba3a167 100644 --- a/resources/views/journal-entries/partials/entry-item-input.blade.php +++ b/resources/views/journal-entries/partials/entry-item-input.blade.php @@ -38,7 +38,8 @@ size="5" class="block w-full" placeholder="Amount" - :value="$amount ?? null" /> + :value="$amount ?? null" + required /> diff --git a/resources/views/recipes/index.blade.php b/resources/views/recipes/index.blade.php index eb17b43..280a828 100644 --- a/resources/views/recipes/index.blade.php +++ b/resources/views/recipes/index.blade.php @@ -3,12 +3,9 @@

Recipes

- - - - - New Recipe - + + Add Recipe +
diff --git a/resources/views/recipes/show.blade.php b/resources/views/recipes/show.blade.php index f60d833..86b3527 100644 --- a/resources/views/recipes/show.blade.php +++ b/resources/views/recipes/show.blade.php @@ -6,25 +6,12 @@

{{ $recipe->name }} - - - - - - - - - - -

@if($recipe->time_total > 0) -
+

Prep time

{{ $recipe->time_prep }} minutes

@@ -56,7 +43,7 @@ @if($item::class === \App\Models\IngredientAmount::class)
  • - {{ \App\Support\Number::fractionStringFromFloat($item->amount) }} + {{ \App\Support\Number::rationalStringFromFloat($item->amount) }} @if($item->unitFormatted){{ $item->unitFormatted }}@endif @if($item->ingredient->type === \App\Models\Recipe::class)
  • +
    + @if(!$recipe->tags->isEmpty()) +
    +

    Tags

    +
    + @foreach($recipe->tags as $tag) + {{ $tag->name }} + @endforeach +
    +
    + @endif +
    -
    diff --git a/tests/Feature/Console/UserAddTest.php b/tests/Feature/Console/UserAddTest.php index d8c8f7e..65c058e 100644 --- a/tests/Feature/Console/UserAddTest.php +++ b/tests/Feature/Console/UserAddTest.php @@ -3,12 +3,10 @@ namespace Tests\Feature\Console; use App\Models\User; -use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; class UserAddTest extends TestCase { - use RefreshDatabase; public function testCanAddUserInteractively(): void { diff --git a/tests/Feature/Http/Controllers/Auth/AuthenticationTest.php b/tests/Feature/Http/Controllers/Auth/AuthenticationTest.php index 4df44cc..2c636b3 100644 --- a/tests/Feature/Http/Controllers/Auth/AuthenticationTest.php +++ b/tests/Feature/Http/Controllers/Auth/AuthenticationTest.php @@ -5,14 +5,11 @@ namespace Tests\Feature\Http\Controllers\Auth; use App\Models\User; use App\Providers\RouteServiceProvider; use Illuminate\Auth\Events\Lockout; -use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Event; -use Illuminate\Validation\ValidationException; use Tests\TestCase; class AuthenticationTest extends TestCase { - use RefreshDatabase; public function testLoginScreenCanRendered(): void { diff --git a/tests/Feature/Http/Controllers/FoodControllerTest.php b/tests/Feature/Http/Controllers/FoodControllerTest.php index cc1f077..a7568eb 100644 --- a/tests/Feature/Http/Controllers/FoodControllerTest.php +++ b/tests/Feature/Http/Controllers/FoodControllerTest.php @@ -5,11 +5,9 @@ namespace Tests\Feature\Http\Controllers; use App\Http\Controllers\FoodController; use App\Models\Food; use Database\Factories\FoodFactory; -use Illuminate\Foundation\Testing\RefreshDatabase; class FoodControllerTest extends HttpControllerTestCase { - use RefreshDatabase; /** * @inheritdoc diff --git a/tests/Feature/Http/Controllers/GoalControllerTest.php b/tests/Feature/Http/Controllers/GoalControllerTest.php index c63c73a..8768912 100644 --- a/tests/Feature/Http/Controllers/GoalControllerTest.php +++ b/tests/Feature/Http/Controllers/GoalControllerTest.php @@ -5,11 +5,9 @@ namespace Tests\Feature\Http\Controllers; use App\Http\Controllers\GoalController; use App\Models\Goal; use Database\Factories\GoalFactory; -use Illuminate\Foundation\Testing\RefreshDatabase; class GoalControllerTest extends HttpControllerTestCase { - use RefreshDatabase; /** * @inheritdoc diff --git a/tests/Feature/Http/Controllers/IngredientPickerControllerTest.php b/tests/Feature/Http/Controllers/IngredientPickerControllerTest.php index a4cc221..7d86a4e 100644 --- a/tests/Feature/Http/Controllers/IngredientPickerControllerTest.php +++ b/tests/Feature/Http/Controllers/IngredientPickerControllerTest.php @@ -6,7 +6,6 @@ use App\Http\Controllers\IngredientPickerController; use App\Models\Food; use App\Models\Recipe; use GuzzleHttp\Exception\ConnectException; -use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Config; use Tests\LoggedInTestCase; @@ -15,7 +14,6 @@ use Tests\LoggedInTestCase; */ class IngredientPickerControllerTest extends LoggedInTestCase { - use RefreshDatabase; private function buildUrl(array $parameters = []): string { diff --git a/tests/Feature/Http/Controllers/JournalEntryControllerTest.php b/tests/Feature/Http/Controllers/JournalEntryControllerTest.php index e4815bf..609c008 100644 --- a/tests/Feature/Http/Controllers/JournalEntryControllerTest.php +++ b/tests/Feature/Http/Controllers/JournalEntryControllerTest.php @@ -6,12 +6,11 @@ use App\Http\Controllers\JournalEntryController; use App\Models\IngredientAmount; use App\Models\JournalEntry; use Database\Factories\JournalEntryFactory; -use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; class JournalEntryControllerTest extends HttpControllerTestCase { - use RefreshDatabase, WithFaker; + use WithFaker; /** * @inheritdoc diff --git a/tests/Feature/Http/Controllers/RecipeControllerTest.php b/tests/Feature/Http/Controllers/RecipeControllerTest.php index fbfe093..84db622 100644 --- a/tests/Feature/Http/Controllers/RecipeControllerTest.php +++ b/tests/Feature/Http/Controllers/RecipeControllerTest.php @@ -9,14 +9,12 @@ use App\Models\RecipeSeparator; use App\Models\RecipeStep; use Database\Factories\RecipeFactory; use Illuminate\Database\Eloquent\Collection; -use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; use Illuminate\Http\UploadedFile; -use Illuminate\Support\Facades\Storage; class RecipeControllerTest extends HttpControllerTestCase { - use RefreshDatabase, WithFaker; + use WithFaker; /** * @inheritdoc diff --git a/tests/Feature/JsonApi/FoodApiTest.php b/tests/Feature/JsonApi/FoodApiTest.php index 9952f39..92a0794 100644 --- a/tests/Feature/JsonApi/FoodApiTest.php +++ b/tests/Feature/JsonApi/FoodApiTest.php @@ -3,15 +3,13 @@ namespace Tests\Feature\JsonApi; use App\Models\Food; -use App\Models\Recipe; use Database\Factories\FoodFactory; -use Illuminate\Database\Eloquent\Collection; -use Illuminate\Foundation\Testing\RefreshDatabase; + use Tests\Feature\JsonApi\Traits\HasTags; class FoodApiTest extends JsonApiTestCase { - use RefreshDatabase, HasTags; + use HasTags; /** * @inheritdoc diff --git a/tests/Feature/JsonApi/GoalApiTest.php b/tests/Feature/JsonApi/GoalApiTest.php index ba870e6..f2f0248 100644 --- a/tests/Feature/JsonApi/GoalApiTest.php +++ b/tests/Feature/JsonApi/GoalApiTest.php @@ -5,12 +5,12 @@ namespace Tests\Feature\JsonApi; use App\Models\Goal; use Database\Factories\GoalFactory; use Illuminate\Database\Eloquent\Collection; -use Illuminate\Foundation\Testing\RefreshDatabase; + use Tests\Feature\JsonApi\Traits\BelongsToUser; class GoalApiTest extends JsonApiTestCase { - use RefreshDatabase, BelongsToUser; + use BelongsToUser; /** * @inheritdoc diff --git a/tests/Feature/JsonApi/IngredientAmountApiTest.php b/tests/Feature/JsonApi/IngredientAmountApiTest.php index 811cc46..6e09806 100644 --- a/tests/Feature/JsonApi/IngredientAmountApiTest.php +++ b/tests/Feature/JsonApi/IngredientAmountApiTest.php @@ -7,11 +7,9 @@ use App\Models\IngredientAmount; use App\Models\JournalEntry; use App\Models\Recipe; use Database\Factories\IngredientAmountFactory; -use Illuminate\Foundation\Testing\RefreshDatabase; class IngredientAmountApiTest extends JsonApiTestCase { - use RefreshDatabase; /** * @inheritdoc diff --git a/tests/Feature/JsonApi/JournalEntryApiTest.php b/tests/Feature/JsonApi/JournalEntryApiTest.php index 8d11783..8288f05 100644 --- a/tests/Feature/JsonApi/JournalEntryApiTest.php +++ b/tests/Feature/JsonApi/JournalEntryApiTest.php @@ -5,12 +5,11 @@ namespace Tests\Feature\JsonApi; use App\Models\JournalEntry; use Database\Factories\JournalEntryFactory; use Illuminate\Database\Eloquent\Collection; -use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\Feature\JsonApi\Traits\BelongsToUser; class JournalEntryApiTest extends JsonApiTestCase { - use RefreshDatabase, BelongsToUser; + use BelongsToUser; /** * @inheritdoc diff --git a/tests/Feature/JsonApi/MediumApiTest.php b/tests/Feature/JsonApi/MediumApiTest.php index c874c68..d18dfce 100644 --- a/tests/Feature/JsonApi/MediumApiTest.php +++ b/tests/Feature/JsonApi/MediumApiTest.php @@ -5,11 +5,9 @@ namespace Tests\Feature\JsonApi; use App\Models\Recipe; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\Factory; -use Illuminate\Foundation\Testing\RefreshDatabase; class MediumApiTest extends JsonApiTestCase { - use RefreshDatabase; /** * @inheritdoc diff --git a/tests/Feature/JsonApi/RecipeApiTest.php b/tests/Feature/JsonApi/RecipeApiTest.php index b10facd..a42fbe6 100644 --- a/tests/Feature/JsonApi/RecipeApiTest.php +++ b/tests/Feature/JsonApi/RecipeApiTest.php @@ -4,12 +4,11 @@ namespace Tests\Feature\JsonApi; use App\Models\Recipe; use Database\Factories\RecipeFactory; -use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\Feature\JsonApi\Traits\HasTags; class RecipeApiTest extends JsonApiTestCase { - use RefreshDatabase, HasTags; + use HasTags; /** * @inheritdoc diff --git a/tests/Feature/JsonApi/RecipeSeparatorApiTest.php b/tests/Feature/JsonApi/RecipeSeparatorApiTest.php index f59d4ae..d546bde 100644 --- a/tests/Feature/JsonApi/RecipeSeparatorApiTest.php +++ b/tests/Feature/JsonApi/RecipeSeparatorApiTest.php @@ -6,12 +6,11 @@ use App\Models\Recipe; use App\Models\RecipeSeparator; use Database\Factories\RecipeSeparatorFactory; use Illuminate\Database\Eloquent\Collection; -use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\Feature\JsonApi\Traits\BelongsToRecipe; class RecipeSeparatorApiTest extends JsonApiTestCase { - use RefreshDatabase, BelongsToRecipe; + use BelongsToRecipe; /** * @inheritdoc diff --git a/tests/Feature/JsonApi/RecipeStepApiTest.php b/tests/Feature/JsonApi/RecipeStepApiTest.php index 7b1bd9e..f09784c 100644 --- a/tests/Feature/JsonApi/RecipeStepApiTest.php +++ b/tests/Feature/JsonApi/RecipeStepApiTest.php @@ -6,12 +6,11 @@ use App\Models\Recipe; use App\Models\RecipeStep; use Database\Factories\RecipeStepFactory; use Illuminate\Database\Eloquent\Collection; -use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\Feature\JsonApi\Traits\BelongsToRecipe; class RecipeStepApiTest extends JsonApiTestCase { - use RefreshDatabase, BelongsToRecipe; + use BelongsToRecipe; /** * @inheritdoc diff --git a/tests/Feature/JsonApi/TagApiTest.php b/tests/Feature/JsonApi/TagApiTest.php index 9e70971..a096130 100644 --- a/tests/Feature/JsonApi/TagApiTest.php +++ b/tests/Feature/JsonApi/TagApiTest.php @@ -4,11 +4,9 @@ namespace Tests\Feature\JsonApi; use App\Models\Tag; use Illuminate\Database\Eloquent\Factories\Factory; -use Illuminate\Foundation\Testing\RefreshDatabase; class TagApiTest extends JsonApiTestCase { - use RefreshDatabase; /** * @inheritdoc diff --git a/tests/Feature/JsonApi/UserApiTest.php b/tests/Feature/JsonApi/UserApiTest.php index d99c670..5fe13c6 100644 --- a/tests/Feature/JsonApi/UserApiTest.php +++ b/tests/Feature/JsonApi/UserApiTest.php @@ -4,11 +4,9 @@ namespace Tests\Feature\JsonApi; use App\Models\User; use Database\Factories\UserFactory; -use Illuminate\Foundation\Testing\RefreshDatabase; class UserApiTest extends JsonApiTestCase { - use RefreshDatabase; /** * @inheritdoc diff --git a/tests/Feature/Support/NutrientsTest.php b/tests/Feature/Support/NutrientsTest.php index 81cc776..58e1958 100644 --- a/tests/Feature/Support/NutrientsTest.php +++ b/tests/Feature/Support/NutrientsTest.php @@ -4,12 +4,10 @@ namespace Tests\Feature\Support; use App\Models\Food; use App\Support\Nutrients; -use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; class NutrientsTest extends TestCase { - use RefreshDatabase; /** * Test invalid Food nutrient multiplier calculation. diff --git a/tests/TestCase.php b/tests/TestCase.php index 2932d4a..29bd88b 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,9 +2,11 @@ namespace Tests; +use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; abstract class TestCase extends BaseTestCase { use CreatesApplication; + use RefreshDatabase; } diff --git a/tests/Unit/Rules/ArrayNotEmptyTest.php b/tests/Unit/Rules/ArrayNotEmptyTest.php index cad3560..3ec9ea7 100644 --- a/tests/Unit/Rules/ArrayNotEmptyTest.php +++ b/tests/Unit/Rules/ArrayNotEmptyTest.php @@ -1,6 +1,5 @@ assertIsString($result); $this->assertEquals($expectedString, $result); } @@ -62,9 +62,9 @@ class NumberTest extends TestCase */ public function fractionStringFloatsProvider(): array { return [ - ['0', 0.0], ['1/8', 1/8], ['1/4', 1/4], ['1/2', 1/2], - ['3/4', 3/4], ['1', 1.0], ['1 1/4', 1.25], - ['1 1/2', 1.5], ['2 1/2', 2.5], ['2 3/4', 2.75], + ['0', 0.0], ['1/8', 1/8], ['1/4', 1/4], ['1/3', 1/3], ['1/2', 1/2], + ['2/3', 2/3], ['3/4', 3/4], ['1', 1.0], ['1 1/4', 1.25], ['1 1/3', 1 + 1/3], + ['1 1/2', 1.5], ['1 2/3', 1 + 2/3], ['2 1/2', 2.5], ['2 3/4', 2.75], ]; } } diff --git a/tests/Unit/Support/NutrientsTest.php b/tests/Unit/Support/NutrientsTest.php new file mode 100644 index 0000000..e7af25c --- /dev/null +++ b/tests/Unit/Support/NutrientsTest.php @@ -0,0 +1,48 @@ +assertIsFloat($result); + $this->assertEquals($expectedFloat, $result); + } + + public function testNutrientRoundingExcepts(): void + { + $this->expectException(\InvalidArgumentException::class); + Nutrients::round(1, 'pancake'); + } + + /** + * Data providers. + */ + + /** + * Provide nutrient amounts for and expected rounded results. + */ + public function nutrientAmountsProvider(): array { + return [ + [0, 'calories', 0], [1, 'calories', 0], [2, 'calories', 0], [3, 'calories', 0], [4, 'calories', 0], [5, 'calories', 5], [21, 'calories', 20], [23, 'calories', 25], [45, 'calories', 45], [50, 'calories', 50], + [0, 'carbohydrates', 0], [0.1, 'carbohydrates', 0], [0.2, 'carbohydrates', 0], [0.3, 'carbohydrates', 0], [0.4, 'carbohydrates', 0], [0.5, 'carbohydrates', 0], [0.9, 'carbohydrates', 0], [1, 'carbohydrates', 1], [2.25, 'carbohydrates', 2], [2.75, 'carbohydrates', 3], + [0, 'protein', 0], [0.1, 'protein', 0], [0.2, 'protein', 0], [0.3, 'protein', 0], [0.4, 'protein', 0], [0.5, 'protein', 0], [0.9, 'protein', 0], [1, 'protein', 1], [2.25, 'protein', 2], [2.75, 'protein', 3], + [0, 'cholesterol', 0], [0.1, 'cholesterol', 0], [0.2, 'cholesterol', 0], [0.3, 'cholesterol', 0], [0.4, 'cholesterol', 0], [0.5, 'cholesterol', 0.5], [0.9, 'cholesterol', 1], [1, 'cholesterol', 1], [1.2, 'cholesterol', 1], [1.4, 'cholesterol', 1.5], [5, 'cholesterol', 5], [6, 'cholesterol', 6], [6.25, 'cholesterol', 6], [6.75, 'cholesterol', 7], + [0, 'fat', 0], [0.1, 'fat', 0], [0.2, 'fat', 0], [0.3, 'fat', 0], [0.4, 'fat', 0], [0.5, 'fat', 0.5], [0.9, 'fat', 1], [1, 'fat', 1], [1.2, 'fat', 1], [1.4, 'fat', 1.5], [5, 'fat', 5], [6, 'fat', 6], [6.25, 'fat', 6], [6.75, 'fat', 7], + [0, 'sodium', 0], [1, 'sodium', 0], [2, 'sodium', 0], [3, 'sodium', 0], [4, 'sodium', 0], [5, 'sodium', 5], [6, 'sodium', 5], [7, 'sodium', 5], [8, 'sodium', 10], [9, 'sodium', 10], [10, 'sodium', 10], [139, 'sodium', 140], [140, 'sodium', 140], [146, 'sodium', 150], [151, 'sodium', 150], [159, 'sodium', 160], + ]; + } +}