ai cooking
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Don't show finalize/assemble button till we've saved something. (#344)

* disable finalize/assemble button till we actually have saves

* always show shoppinglist

* tailwind and bettter comment

---------

Co-authored-by: paul miller <paul.miller>

authored by

Paul Miller
paul miller
and committed by
GitHub
21973ccb 9c18b7dc

+215 -31
+40 -4
internal/recipes/buttons_test.go
··· 80 80 if !strings.Contains(html, `Assemble Shopping List`) { 81 81 t.Error("HTML should contain Assemble Shopping List button") 82 82 } 83 - 84 - // Check for finalize HTMX button 85 - if !strings.Contains(html, `hx-post="/recipes/`) || !strings.Contains(html, `/finalize"`) { 86 - t.Error("HTML should submit finalize with HTMX POST") 83 + if !strings.Contains(html, `disabled`) { 84 + t.Error("HTML should disable finalize button when no recipes are saved") 85 + } 86 + if strings.Contains(html, `/finalize"`) { 87 + t.Error("HTML should not wire finalize endpoint when button is disabled") 87 88 } 89 + if !strings.Contains(html, `Save at least one recipe to assemble your shopping list.`) { 90 + t.Error("HTML should explain how to enable finalize button") 91 + } 92 + 88 93 if !strings.Contains(html, `/recipes/`) || !strings.Contains(html, `/regenerate"`) { 89 94 t.Error("HTML should submit regenerate with POST endpoint") 90 95 } ··· 100 105 t.Error("HTML should contain Recipe Two title") 101 106 } 102 107 } 108 + 109 + func TestFormatShoppingListHTML_EnablesFinalizeWhenRecipeSaved(t *testing.T) { 110 + listWithSavedRecipe := ai.ShoppingList{ 111 + Recipes: []ai.Recipe{ 112 + { 113 + Title: "Saved Recipe", 114 + Description: "Saved recipe", 115 + Ingredients: []ai.Ingredient{{Name: "ingredient1", Quantity: "1 cup", Price: "2.00"}}, 116 + Instructions: []string{ 117 + "Step 1", 118 + }, 119 + Health: "Healthy", 120 + DrinkPairing: "Water", 121 + }, 122 + }, 123 + } 124 + 125 + loc := locations.Location{ID: "70000001", Name: "Store", Address: "1 Main St"} 126 + p := DefaultParams(&loc, time.Now()) 127 + p.Saved = []ai.Recipe{listWithSavedRecipe.Recipes[0]} 128 + w := httptest.NewRecorder() 129 + FormatShoppingListHTML(p, listWithSavedRecipe, true, w) 130 + html := w.Body.String() 131 + 132 + if !strings.Contains(html, `hx-post="/recipes/`) || !strings.Contains(html, `/finalize"`) { 133 + t.Error("HTML should submit finalize with HTMX POST when a recipe is saved") 134 + } 135 + if strings.Contains(html, `id="finalize-help"`) { 136 + t.Error("HTML should not render finalize helper text when button is enabled") 137 + } 138 + }
+15 -4
internal/recipes/html.go
··· 75 75 }) 76 76 combinedIngredients = append(combinedIngredients, displayIngredients...) 77 77 } 78 - var shoppingList []ai.Ingredient 79 - if len(l.Recipes) > 1 { 80 - shoppingList = shoppingListForDisplay(combinedIngredients) 81 - } 78 + shoppingList := shoppingListForDisplay(combinedIngredients) 82 79 data := struct { 83 80 Location locations.Location 84 81 Date string ··· 88 85 Hash string 89 86 Recipes []shoppingRecipeView 90 87 ShoppingList []ai.Ingredient 88 + HasSavedRecipes bool 91 89 ConversationID string 92 90 Style seasons.Style 93 91 ServerSignedIn bool ··· 100 98 Hash: hash, 101 99 Recipes: recipeViews, 102 100 ShoppingList: shoppingList, 101 + HasSavedRecipes: len(p.Saved) > 0, 103 102 ConversationID: l.ConversationID, 104 103 Style: seasons.GetCurrentStyle(), 105 104 ServerSignedIn: signedIn, ··· 217 216 if err := templates.ShoppingList.ExecuteTemplate(writer, templateName, data); err != nil { 218 217 http.Error(writer, "shopping list wine template error: "+err.Error(), http.StatusInternalServerError) 219 218 } 219 + } 220 + 221 + func RenderShoppingFinalizeControlsHTML(hash string, hasSavedRecipes bool, writer io.Writer) error { 222 + data := struct { 223 + Hash string 224 + HasSavedRecipes bool 225 + }{ 226 + Hash: hash, 227 + HasSavedRecipes: hasSavedRecipes, 228 + } 229 + 230 + return templates.ShoppingList.ExecuteTemplate(writer, "shopping_finalize_controls_response", data) 220 231 } 221 232 222 233 // drops clarity, instructions and most of shoppinglist
+9
internal/recipes/html_test.go
··· 76 76 if !strings.Contains(html, "shopping-wine-refresh:") { 77 77 t.Error("shopping list HTML should include wine refresh history handling") 78 78 } 79 + if !strings.Contains(html, "Shopping list") { 80 + t.Error("shopping list HTML should render the shopping list section for a single recipe") 81 + } 82 + if !strings.Contains(html, `id="finalize-help"`) { 83 + t.Error("shopping list HTML should include helper text for disabled finalize state") 84 + } 85 + if !strings.Contains(html, `disabled`) { 86 + t.Error("shopping list HTML should disable finalize button when nothing is saved") 87 + } 79 88 } 80 89 81 90 func TestFormatMail_ValidHTML(t *testing.T) {
+43 -3
internal/recipes/server.go
··· 1 1 package recipes 2 2 3 3 import ( 4 + "bytes" 4 5 "careme/internal/ai" 5 6 "careme/internal/auth" 6 7 "careme/internal/cache" ··· 460 461 return 461 462 } 462 463 464 + p, err := s.paramsForAction(ctx, selectionHash, currentUser.ID, "") 465 + if err != nil { 466 + slog.ErrorContext(ctx, "failed to load params for save response", "user_id", currentUser.ID, "selection_hash", selectionHash, "error", err) 467 + http.Error(w, "failed to save recipe", http.StatusInternalServerError) 468 + return 469 + } 470 + 471 + var response bytes.Buffer 472 + if _, err := fmt.Fprint(&response, `<span class="text-xs font-medium text-action-green-700">Saved to kitchen</span>`); err != nil { 473 + slog.ErrorContext(ctx, "failed to build save response", "hash", recipeHash, "error", err) 474 + http.Error(w, "failed to write response", http.StatusInternalServerError) 475 + return 476 + } 477 + if err := RenderShoppingFinalizeControlsHTML(selectionHash, len(p.Saved) > 0, &response); err != nil { 478 + slog.ErrorContext(ctx, "failed to render finalize controls after save", "selection_hash", selectionHash, "error", err) 479 + http.Error(w, "failed to write response", http.StatusInternalServerError) 480 + return 481 + } 482 + 463 483 setTextContent(w) 464 - _, err = fmt.Fprint(w, `<span class="text-xs font-medium text-action-green-700">Saved to kitchen</span>`) 484 + _, err = w.Write(response.Bytes()) 465 485 if err != nil { 466 486 slog.ErrorContext(ctx, "failed to write save response", "hash", recipeHash, "error", err) 467 487 http.Error(w, "failed to write response", http.StatusInternalServerError) ··· 520 540 return 521 541 } 522 542 543 + p, err := s.paramsForAction(ctx, selectionHash, currentUser.ID, "") 544 + if err != nil { 545 + slog.ErrorContext(ctx, "failed to load params for dismiss response", "user_id", currentUser.ID, "selection_hash", selectionHash, "error", err) 546 + http.Error(w, "failed to dismiss recipe", http.StatusInternalServerError) 547 + return 548 + } 549 + 550 + var response bytes.Buffer 551 + if _, err := fmt.Fprint(&response, `<span class="text-xs font-medium text-action-red-700">Removed from kitchen</span>`); err != nil { 552 + slog.ErrorContext(ctx, "failed to build dismiss response", "hash", recipeHash, "error", err) 553 + http.Error(w, "failed to write response", http.StatusInternalServerError) 554 + return 555 + } 556 + if err := RenderShoppingFinalizeControlsHTML(selectionHash, len(p.Saved) > 0, &response); err != nil { 557 + slog.ErrorContext(ctx, "failed to render finalize controls after dismiss", "selection_hash", selectionHash, "error", err) 558 + http.Error(w, "failed to write response", http.StatusInternalServerError) 559 + return 560 + } 561 + 523 562 setTextContent(w) 524 - _, err = fmt.Fprint(w, `<span class="text-xs font-medium text-action-red-700">Removed from kitchen</span>`) 563 + _, err = w.Write(response.Bytes()) 525 564 if err != nil { 526 565 slog.ErrorContext(ctx, "failed to write dismiss response", "hash", recipeHash, "error", err) 527 566 http.Error(w, "failed to write response", http.StatusInternalServerError) ··· 601 640 return 602 641 } 603 642 if len(p.Saved) == 0 { 604 - //ui should ideally not allow us to get here 643 + //ui does not allow this 644 + slog.ErrorContext(ctx, "Got zero saved recipes finalize", "hash", hash) 605 645 http.Error(w, "no recipes selected to save", http.StatusBadRequest) 606 646 return 607 647 }
+71 -10
internal/recipes/server_test.go
··· 849 849 Title: "Save Me", 850 850 Description: "Recipe to save", 851 851 } 852 + p := DefaultParams(&locations.Location{ID: "70004001", Name: "Store"}, time.Now()) 853 + p.ConversationID = "conv-123" 854 + originHash := p.Hash() 855 + if err := s.SaveParams(t.Context(), p); err != nil { 856 + t.Fatalf("failed to save params: %v", err) 857 + } 852 858 recipeHash := recipe.ComputeHash() 853 - if err := s.SaveRecipes(t.Context(), []ai.Recipe{recipe}, "origin-hash"); err != nil { 859 + if err := s.SaveRecipes(t.Context(), []ai.Recipe{recipe}, originHash); err != nil { 854 860 t.Fatalf("failed to save recipe in cache: %v", err) 855 861 } 862 + if err := s.SaveShoppingList(t.Context(), &ai.ShoppingList{ 863 + Recipes: []ai.Recipe{recipe}, 864 + ConversationID: "conv-123", 865 + }, originHash); err != nil { 866 + t.Fatalf("failed to save shopping list: %v", err) 867 + } 856 868 857 - form := url.Values{"h": {"origin-hash"}} 869 + form := url.Values{"h": {originHash}} 858 870 req := httptest.NewRequest(http.MethodPost, "/recipe/"+recipeHash+"/save", strings.NewReader(form.Encode())) 859 871 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 860 872 req.Header.Set("HX-Request", "true") ··· 869 881 if !strings.Contains(rr.Body.String(), "Saved to kitchen") { 870 882 t.Fatalf("expected success response, got body: %s", rr.Body.String()) 871 883 } 884 + if !strings.Contains(rr.Body.String(), `id="shopping-finalize-controls"`) || !strings.Contains(rr.Body.String(), `hx-swap-oob="outerHTML"`) { 885 + t.Fatalf("expected finalize controls oob response, got body: %s", rr.Body.String()) 886 + } 887 + if !strings.Contains(rr.Body.String(), `/recipes/`+originHash+`/finalize`) { 888 + t.Fatalf("expected finalize button to become enabled after save, got body: %s", rr.Body.String()) 889 + } 872 890 873 891 user, err := storage.GetByID("mock-clerk-user-id") 874 892 if err != nil { ··· 880 898 if user.LastRecipes[0].Hash != recipeHash { 881 899 t.Fatalf("expected saved hash %q, got %q", recipeHash, user.LastRecipes[0].Hash) 882 900 } 883 - selection, err := s.loadRecipeSelection(t.Context(), "mock-clerk-user-id", "origin-hash") 901 + selection, err := s.loadRecipeSelection(t.Context(), "mock-clerk-user-id", originHash) 884 902 if err != nil { 885 903 t.Fatalf("failed to load selection: %v", err) 886 904 } ··· 925 943 Title: "Save Me", 926 944 Description: "Recipe to save", 927 945 } 946 + currentParams := DefaultParams(&locations.Location{ID: "70004001", Name: "Store"}, time.Now()) 947 + currentParams.ConversationID = "conv-123" 948 + currentHash := currentParams.Hash() 949 + if err := s.SaveParams(t.Context(), currentParams); err != nil { 950 + t.Fatalf("failed to save params: %v", err) 951 + } 928 952 recipeHash := recipe.ComputeHash() 929 953 if err := s.SaveRecipes(t.Context(), []ai.Recipe{recipe}, "stale-origin-hash"); err != nil { 930 954 t.Fatalf("failed to save recipe in cache: %v", err) 931 955 } 956 + if err := s.SaveShoppingList(t.Context(), &ai.ShoppingList{ 957 + Recipes: []ai.Recipe{recipe}, 958 + ConversationID: "conv-123", 959 + }, currentHash); err != nil { 960 + t.Fatalf("failed to save shopping list: %v", err) 961 + } 932 962 933 - req := httptest.NewRequest(http.MethodPost, "/recipe/"+recipeHash+"/save?h=current-list-hash", nil) 963 + req := httptest.NewRequest(http.MethodPost, "/recipe/"+recipeHash+"/save?h="+currentHash, nil) 934 964 req.Header.Set("HX-Request", "true") 935 965 req.SetPathValue("hash", recipeHash) 936 966 rr := httptest.NewRecorder() ··· 940 970 if rr.Code != http.StatusOK { 941 971 t.Fatalf("expected status %d, got %d", http.StatusOK, rr.Code) 942 972 } 943 - currentSelection, err := s.loadRecipeSelection(t.Context(), "mock-clerk-user-id", "current-list-hash") 973 + currentSelection, err := s.loadRecipeSelection(t.Context(), "mock-clerk-user-id", currentHash) 944 974 if err != nil { 945 975 t.Fatalf("failed to load current hash selection: %v", err) 946 976 } ··· 969 999 Title: "Dismiss Recipe", 970 1000 Description: "Recipe to dismiss", 971 1001 } 1002 + p := DefaultParams(&locations.Location{ID: "70004001", Name: "Store"}, time.Now()) 1003 + p.ConversationID = "conv-123" 1004 + p.Saved = []ai.Recipe{recipe} 1005 + originHash := p.Hash() 1006 + if err := s.SaveParams(t.Context(), p); err != nil { 1007 + t.Fatalf("failed to save params: %v", err) 1008 + } 972 1009 recipeHash := recipe.ComputeHash() 973 - if err := s.SaveRecipes(t.Context(), []ai.Recipe{recipe}, "origin-hash"); err != nil { 1010 + if err := s.SaveRecipes(t.Context(), []ai.Recipe{recipe}, originHash); err != nil { 974 1011 t.Fatalf("failed to save recipe in cache: %v", err) 975 1012 } 1013 + if err := s.SaveShoppingList(t.Context(), &ai.ShoppingList{ 1014 + Recipes: []ai.Recipe{recipe}, 1015 + ConversationID: "conv-123", 1016 + }, originHash); err != nil { 1017 + t.Fatalf("failed to save shopping list: %v", err) 1018 + } 976 1019 977 1020 user := &utypes.User{ 978 1021 ID: "mock-clerk-user-id", ··· 996 1039 t.Fatalf("failed to create user: %v", err) 997 1040 } 998 1041 999 - form := url.Values{"h": {"origin-hash"}} 1042 + form := url.Values{"h": {originHash}} 1000 1043 req := httptest.NewRequest(http.MethodPost, "/recipe/"+recipeHash+"/dismiss", strings.NewReader(form.Encode())) 1001 1044 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 1002 1045 req.Header.Set("HX-Request", "true") ··· 1011 1054 if !strings.Contains(rr.Body.String(), "Removed from kitchen") { 1012 1055 t.Fatalf("expected dismiss response, got body: %s", rr.Body.String()) 1013 1056 } 1057 + if !strings.Contains(rr.Body.String(), `id="shopping-finalize-controls"`) || !strings.Contains(rr.Body.String(), `hx-swap-oob="outerHTML"`) { 1058 + t.Fatalf("expected finalize controls oob response, got body: %s", rr.Body.String()) 1059 + } 1060 + if !strings.Contains(rr.Body.String(), `disabled`) || !strings.Contains(rr.Body.String(), `Save at least one recipe to assemble your shopping list.`) { 1061 + t.Fatalf("expected finalize button to become disabled after dismiss, got body: %s", rr.Body.String()) 1062 + } 1014 1063 1015 1064 updated, err := storage.GetByID("mock-clerk-user-id") 1016 1065 if err != nil { ··· 1022 1071 if updated.LastRecipes[0].Hash != "keep-hash" { 1023 1072 t.Fatalf("expected remaining hash keep-hash, got %q", updated.LastRecipes[0].Hash) 1024 1073 } 1025 - selection, err := s.loadRecipeSelection(t.Context(), "mock-clerk-user-id", "origin-hash") 1074 + selection, err := s.loadRecipeSelection(t.Context(), "mock-clerk-user-id", originHash) 1026 1075 if err != nil { 1027 1076 t.Fatalf("failed to load selection: %v", err) 1028 1077 } ··· 1066 1115 recipe := ai.Recipe{ 1067 1116 Title: "Dismiss Recipe", 1068 1117 Description: "Recipe to dismiss", 1118 + } 1119 + currentParams := DefaultParams(&locations.Location{ID: "70004001", Name: "Store"}, time.Now()) 1120 + currentParams.ConversationID = "conv-123" 1121 + currentHash := currentParams.Hash() 1122 + if err := s.SaveParams(t.Context(), currentParams); err != nil { 1123 + t.Fatalf("failed to save params: %v", err) 1069 1124 } 1070 1125 recipeHash := recipe.ComputeHash() 1071 1126 if err := s.SaveRecipes(t.Context(), []ai.Recipe{recipe}, "stale-origin-hash"); err != nil { 1072 1127 t.Fatalf("failed to save recipe in cache: %v", err) 1073 1128 } 1129 + if err := s.SaveShoppingList(t.Context(), &ai.ShoppingList{ 1130 + Recipes: []ai.Recipe{recipe}, 1131 + ConversationID: "conv-123", 1132 + }, currentHash); err != nil { 1133 + t.Fatalf("failed to save shopping list: %v", err) 1134 + } 1074 1135 1075 1136 user := &utypes.User{ 1076 1137 ID: "mock-clerk-user-id", ··· 1089 1150 t.Fatalf("failed to create user: %v", err) 1090 1151 } 1091 1152 1092 - req := httptest.NewRequest(http.MethodPost, "/recipe/"+recipeHash+"/dismiss?h=current-list-hash", nil) 1153 + req := httptest.NewRequest(http.MethodPost, "/recipe/"+recipeHash+"/dismiss?h="+currentHash, nil) 1093 1154 req.Header.Set("HX-Request", "true") 1094 1155 req.SetPathValue("hash", recipeHash) 1095 1156 rr := httptest.NewRecorder() ··· 1099 1160 if rr.Code != http.StatusOK { 1100 1161 t.Fatalf("expected status %d, got %d", http.StatusOK, rr.Code) 1101 1162 } 1102 - currentSelection, err := s.loadRecipeSelection(t.Context(), "mock-clerk-user-id", "current-list-hash") 1163 + currentSelection, err := s.loadRecipeSelection(t.Context(), "mock-clerk-user-id", currentHash) 1103 1164 if err != nil { 1104 1165 t.Fatalf("failed to load current hash selection: %v", err) 1105 1166 }
+1 -1
internal/static/tailwind.css
··· 1 1 /*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */ 2 - @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-700:oklch(50.5% .213 27.518);--color-amber-500:oklch(76.9% .188 70.08);--color-green-700:oklch(52.7% .154 150.069);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-700:oklch(50.8% .118 165.612);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-900:oklch(20.8% .042 265.755);--color-slate-950:oklch(12.9% .042 264.695);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-900:oklch(21% .034 264.665);--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-md:28rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--tracking-tight:-.025em;--tracking-wide:.025em;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-brand-50:var(--brand-50);--color-brand-100:var(--brand-100);--color-brand-200:var(--brand-200);--color-brand-300:var(--brand-300);--color-brand-400:var(--brand-400);--color-brand-500:var(--brand-500);--color-brand-600:var(--brand-600);--color-brand-700:var(--brand-700);--color-brand-800:var(--brand-800);--color-brand-900:var(--brand-900);--color-ink-500:#64748b;--color-ink-600:#475569;--color-ink-700:#334155;--color-ink-900:#0f172a;--color-action-green-50:#ecfdf5;--color-action-green-100:#d1fae5;--color-action-green-300:#6ee7b7;--color-action-green-500:#10b981;--color-action-green-600:#059669;--color-action-green-700:#047857;--color-action-red-50:#fef2f2;--color-action-red-100:#fee2e2;--color-action-red-500:#ef4444;--color-action-red-600:#dc2626;--color-action-red-700:#b91c1c}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}p{text-wrap:pretty}}@layer components{.friendly-card{corner-shape:squircle;border-radius:48px}.friendly-card-soft{corner-shape:squircle;border-radius:28px}.shopping-wine-details,.shopping-recipe-card:has(details[open]) .shopping-wine-preview{display:none}.shopping-recipe-card:has(details[open]) .shopping-wine-details{display:block}}@layer utilities{.pointer-events-none{pointer-events:none}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing)*0)}.top-3{top:calc(var(--spacing)*3)}.right-0{right:calc(var(--spacing)*0)}.z-10{z-index:10}.z-20{z-index:20}.-mx-2{margin-inline:calc(var(--spacing)*-2)}.mx-auto{margin-inline:auto}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-6{margin-top:calc(var(--spacing)*6)}.mt-8{margin-top:calc(var(--spacing)*8)}.mt-10{margin-top:calc(var(--spacing)*10)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-flex{display:inline-flex}.h-4{height:calc(var(--spacing)*4)}.h-14{height:calc(var(--spacing)*14)}.h-full{height:100%}.min-h-screen{min-height:100vh}.w-4{width:calc(var(--spacing)*4)}.w-14{width:calc(var(--spacing)*14)}.w-48{width:calc(var(--spacing)*48)}.w-full{width:100%}.max-w-3xl{max-width:var(--container-3xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-md{max-width:var(--container-md)}.max-w-xs{max-width:var(--container-xs)}.flex-1{flex:1}.animate-spin{animation:var(--animate-spin)}.cursor-pointer{cursor:pointer}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.list-none{list-style-type:none}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1{gap:calc(var(--spacing)*1)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-6{gap:calc(var(--spacing)*6)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*5)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*5)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*8)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*8)*calc(1 - var(--tw-space-y-reverse)))}.gap-x-2{column-gap:calc(var(--spacing)*2)}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-brand-100>:not(:last-child)){border-color:var(--color-brand-100)}.overflow-hidden{overflow:hidden}.overflow-x-hidden{overflow-x:hidden}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-4{border-style:var(--tw-border-style);border-width:4px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-action-green-500{border-color:var(--color-action-green-500)}.border-action-red-500{border-color:var(--color-action-red-500)}.border-brand-100{border-color:var(--color-brand-100)}.border-brand-200{border-color:var(--color-brand-200)}.border-brand-300{border-color:var(--color-brand-300)}.border-brand-400{border-color:var(--color-brand-400)}.border-emerald-200{border-color:var(--color-emerald-200)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-t-brand-600{border-top-color:var(--color-brand-600)}.bg-action-green-50{background-color:var(--color-action-green-50)}.bg-action-green-500{background-color:var(--color-action-green-500)}.bg-action-red-50{background-color:var(--color-action-red-50)}.bg-brand-50,.bg-brand-50\/40{background-color:var(--color-brand-50)}@supports (color:color-mix(in lab, red, red)){.bg-brand-50\/40{background-color:color-mix(in oklab,var(--color-brand-50)40%,transparent)}}.bg-brand-50\/60{background-color:var(--color-brand-50)}@supports (color:color-mix(in lab, red, red)){.bg-brand-50\/60{background-color:color-mix(in oklab,var(--color-brand-50)60%,transparent)}}.bg-brand-50\/70{background-color:var(--color-brand-50)}@supports (color:color-mix(in lab, red, red)){.bg-brand-50\/70{background-color:color-mix(in oklab,var(--color-brand-50)70%,transparent)}}.bg-brand-50\/80{background-color:var(--color-brand-50)}@supports (color:color-mix(in lab, red, red)){.bg-brand-50\/80{background-color:color-mix(in oklab,var(--color-brand-50)80%,transparent)}}.bg-brand-100{background-color:var(--color-brand-100)}.bg-brand-500{background-color:var(--color-brand-500)}.bg-brand-600{background-color:var(--color-brand-600)}.bg-emerald-50{background-color:var(--color-emerald-50)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-white{background-color:var(--color-white)}.bg-white\/60{background-color:#fff9}@supports (color:color-mix(in lab, red, red)){.bg-white\/60{background-color:color-mix(in oklab,var(--color-white)60%,transparent)}}.bg-white\/88{background-color:#ffffffe0}@supports (color:color-mix(in lab, red, red)){.bg-white\/88{background-color:color-mix(in oklab,var(--color-white)88%,transparent)}}.bg-white\/90{background-color:#ffffffe6}@supports (color:color-mix(in lab, red, red)){.bg-white\/90{background-color:color-mix(in oklab,var(--color-white)90%,transparent)}}.bg-white\/95{background-color:#fffffff2}@supports (color:color-mix(in lab, red, red)){.bg-white\/95{background-color:color-mix(in oklab,var(--color-white)95%,transparent)}}.bg-gradient-to-b{--tw-gradient-position:to bottom in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-brand-50{--tw-gradient-from:var(--color-brand-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-brand-50\/80{--tw-gradient-from:var(--color-brand-50)}@supports (color:color-mix(in lab, red, red)){.from-brand-50\/80{--tw-gradient-from:color-mix(in oklab,var(--color-brand-50)80%,transparent)}}.from-brand-50\/80{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-white\/72{--tw-gradient-from:#ffffffb8}@supports (color:color-mix(in lab, red, red)){.from-white\/72{--tw-gradient-from:color-mix(in oklab,var(--color-white)72%,transparent)}}.from-white\/72{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.via-white{--tw-gradient-via:var(--color-white);--tw-gradient-via-stops:var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-via)var(--tw-gradient-via-position),var(--tw-gradient-to)var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-white\/62{--tw-gradient-via:#ffffff9e}@supports (color:color-mix(in lab, red, red)){.via-white\/62{--tw-gradient-via:color-mix(in oklab,var(--color-white)62%,transparent)}}.via-white\/62{--tw-gradient-via-stops:var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-via)var(--tw-gradient-via-position),var(--tw-gradient-to)var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.to-brand-50\/80{--tw-gradient-to:var(--color-brand-50)}@supports (color:color-mix(in lab, red, red)){.to-brand-50\/80{--tw-gradient-to:color-mix(in oklab,var(--color-brand-50)80%,transparent)}}.to-brand-50\/80{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-white{--tw-gradient-to:var(--color-white);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-white\/78{--tw-gradient-to:#ffffffc7}@supports (color:color-mix(in lab, red, red)){.to-white\/78{--tw-gradient-to:color-mix(in oklab,var(--color-white)78%,transparent)}}.to-white\/78{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.object-cover{object-fit:cover}.p-1{padding:calc(var(--spacing)*1)}.p-2{padding:calc(var(--spacing)*2)}.p-4{padding:calc(var(--spacing)*4)}.p-5{padding:calc(var(--spacing)*5)}.p-6{padding:calc(var(--spacing)*6)}.p-8{padding:calc(var(--spacing)*8)}.p-10{padding:calc(var(--spacing)*10)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-5{padding-inline:calc(var(--spacing)*5)}.px-8{padding-inline:calc(var(--spacing)*8)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-2\.5{padding-block:calc(var(--spacing)*2.5)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-6{padding-block:calc(var(--spacing)*6)}.py-8{padding-block:calc(var(--spacing)*8)}.py-10{padding-block:calc(var(--spacing)*10)}.pt-1{padding-top:calc(var(--spacing)*1)}.pt-2{padding-top:calc(var(--spacing)*2)}.pt-8{padding-top:calc(var(--spacing)*8)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.pb-6{padding-bottom:calc(var(--spacing)*6)}.pl-5{padding-left:calc(var(--spacing)*5)}.pl-6{padding-left:calc(var(--spacing)*6)}.text-center{text-align:center}.text-left{text-align:left}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-none{--tw-leading:1;line-height:1}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.text-pretty{text-wrap:pretty}.whitespace-pre-line{white-space:pre-line}.text-action-green-700{color:var(--color-action-green-700)}.text-action-red-600{color:var(--color-action-red-600)}.text-action-red-700{color:var(--color-action-red-700)}.text-amber-500{color:var(--color-amber-500)}.text-brand-600{color:var(--color-brand-600)}.text-brand-700{color:var(--color-brand-700)}.text-brand-800{color:var(--color-brand-800)}.text-brand-900{color:var(--color-brand-900)}.text-emerald-700{color:var(--color-emerald-700)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-900{color:var(--color-gray-900)}.text-green-700{color:var(--color-green-700)}.text-ink-500{color:var(--color-ink-500)}.text-ink-600{color:var(--color-ink-600)}.text-ink-700{color:var(--color-ink-700)}.text-ink-900{color:var(--color-ink-900)}.text-red-700{color:var(--color-red-700)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.underline{text-decoration-line:underline}.decoration-brand-300{-webkit-text-decoration-color:var(--color-brand-300);-webkit-text-decoration-color:var(--color-brand-300);text-decoration-color:var(--color-brand-300)}.underline-offset-2{text-underline-offset:2px}.underline-offset-4{text-underline-offset:4px}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.placeholder-gray-400::placeholder{color:var(--color-gray-400)}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.backdrop-blur-\[2px\]{--tw-backdrop-blur:blur(2px);-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.peer-checked\/dismiss\:border-action-red-700:is(:where(.peer\/dismiss):checked~*){border-color:var(--color-action-red-700)}.peer-checked\/dismiss\:bg-action-red-600:is(:where(.peer\/dismiss):checked~*){background-color:var(--color-action-red-600)}.peer-checked\/dismiss\:text-white:is(:where(.peer\/dismiss):checked~*){color:var(--color-white)}.peer-checked\/instructions\:block:is(:where(.peer\/instructions):checked~*){display:block}.peer-checked\/save\:border-action-green-700:is(:where(.peer\/save):checked~*){border-color:var(--color-action-green-700)}.peer-checked\/save\:bg-action-green-600:is(:where(.peer\/save):checked~*){background-color:var(--color-action-green-600)}.peer-checked\/save\:text-white:is(:where(.peer\/save):checked~*){color:var(--color-white)}@media (hover:hover){.hover\:border-brand-400:hover{border-color:var(--color-brand-400)}.hover\:bg-action-green-100:hover{background-color:var(--color-action-green-100)}.hover\:bg-action-green-600:hover{background-color:var(--color-action-green-600)}.hover\:bg-action-red-50:hover{background-color:var(--color-action-red-50)}.hover\:bg-action-red-100:hover{background-color:var(--color-action-red-100)}.hover\:bg-brand-50:hover{background-color:var(--color-brand-50)}.hover\:bg-brand-100:hover{background-color:var(--color-brand-100)}.hover\:bg-brand-200:hover{background-color:var(--color-brand-200)}.hover\:bg-brand-600:hover{background-color:var(--color-brand-600)}.hover\:bg-brand-700:hover{background-color:var(--color-brand-700)}.hover\:bg-white\/70:hover{background-color:#ffffffb3}@supports (color:color-mix(in lab, red, red)){.hover\:bg-white\/70:hover{background-color:color-mix(in oklab,var(--color-white)70%,transparent)}}.hover\:text-amber-500:hover{color:var(--color-amber-500)}.hover\:text-brand-600:hover{color:var(--color-brand-600)}.hover\:text-brand-700:hover{color:var(--color-brand-700)}.hover\:text-brand-800:hover{color:var(--color-brand-800)}.hover\:underline:hover{text-decoration-line:underline}}.focus\:border-brand-400:focus{border-color:var(--color-brand-400)}.focus\:border-brand-500:focus{border-color:var(--color-brand-500)}.focus\:bg-brand-50:focus{background-color:var(--color-brand-50)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-action-green-300:focus{--tw-ring-color:var(--color-action-green-300)}.focus\:ring-brand-300:focus{--tw-ring-color:var(--color-brand-300)}.focus\:ring-brand-400:focus{--tw-ring-color:var(--color-brand-400)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-brand-400:disabled{background-color:var(--color-brand-400)}.disabled\:text-brand-400:disabled{color:var(--color-brand-400)}.disabled\:text-white\/90:disabled{color:#ffffffe6}@supports (color:color-mix(in lab, red, red)){.disabled\:text-white\/90:disabled{color:color-mix(in oklab,var(--color-white)90%,transparent)}}@media (min-width:40rem){.sm\:w-40{width:calc(var(--spacing)*40)}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:items-end{align-items:flex-end}.sm\:justify-between{justify-content:space-between}.sm\:justify-end{justify-content:flex-end}.sm\:py-10{padding-block:calc(var(--spacing)*10)}}@media (min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:64rem){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}}@supports (color:oklch(62% 0.12 220)){:root{--color-ink-500:oklch(55% .032 248);--color-ink-600:oklch(46% .034 248);--color-ink-700:oklch(38.5% .035 250);--color-ink-900:oklch(24% .033 254);--color-action-green-50:oklch(97.6% .02 154);--color-action-green-100:oklch(94.5% .042 154);--color-action-green-300:oklch(82.8% .111 154);--color-action-green-500:oklch(68.5% .169 154);--color-action-green-600:oklch(58.8% .158 154);--color-action-green-700:oklch(50% .134 154);--color-action-red-50:oklch(97.6% .02 28);--color-action-red-100:oklch(94.5% .042 28);--color-action-red-300:oklch(82.8% .111 28);--color-action-red-500:oklch(68.5% .169 28);--color-action-red-600:oklch(58.8% .158 28);--color-action-red-700:oklch(50% .134 28)}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}} 2 + @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-700:oklch(50.5% .213 27.518);--color-amber-500:oklch(76.9% .188 70.08);--color-green-700:oklch(52.7% .154 150.069);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-700:oklch(50.8% .118 165.612);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-900:oklch(20.8% .042 265.755);--color-slate-950:oklch(12.9% .042 264.695);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-900:oklch(21% .034 264.665);--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-md:28rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--tracking-tight:-.025em;--tracking-wide:.025em;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-brand-50:var(--brand-50);--color-brand-100:var(--brand-100);--color-brand-200:var(--brand-200);--color-brand-300:var(--brand-300);--color-brand-400:var(--brand-400);--color-brand-500:var(--brand-500);--color-brand-600:var(--brand-600);--color-brand-700:var(--brand-700);--color-brand-800:var(--brand-800);--color-brand-900:var(--brand-900);--color-ink-500:#64748b;--color-ink-600:#475569;--color-ink-700:#334155;--color-ink-900:#0f172a;--color-action-green-50:#ecfdf5;--color-action-green-100:#d1fae5;--color-action-green-300:#6ee7b7;--color-action-green-500:#10b981;--color-action-green-600:#059669;--color-action-green-700:#047857;--color-action-red-50:#fef2f2;--color-action-red-100:#fee2e2;--color-action-red-500:#ef4444;--color-action-red-600:#dc2626;--color-action-red-700:#b91c1c}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}p{text-wrap:pretty}}@layer components{.friendly-card{corner-shape:squircle;border-radius:48px}.friendly-card-soft{corner-shape:squircle;border-radius:28px}.shopping-wine-details,.shopping-recipe-card:has(details[open]) .shopping-wine-preview{display:none}.shopping-recipe-card:has(details[open]) .shopping-wine-details{display:block}}@layer utilities{.pointer-events-none{pointer-events:none}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing)*0)}.top-3{top:calc(var(--spacing)*3)}.right-0{right:calc(var(--spacing)*0)}.z-10{z-index:10}.z-20{z-index:20}.-mx-2{margin-inline:calc(var(--spacing)*-2)}.mx-auto{margin-inline:auto}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-6{margin-top:calc(var(--spacing)*6)}.mt-8{margin-top:calc(var(--spacing)*8)}.mt-10{margin-top:calc(var(--spacing)*10)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-flex{display:inline-flex}.h-4{height:calc(var(--spacing)*4)}.h-14{height:calc(var(--spacing)*14)}.h-full{height:100%}.min-h-screen{min-height:100vh}.w-4{width:calc(var(--spacing)*4)}.w-14{width:calc(var(--spacing)*14)}.w-48{width:calc(var(--spacing)*48)}.w-full{width:100%}.max-w-3xl{max-width:var(--container-3xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-md{max-width:var(--container-md)}.max-w-xs{max-width:var(--container-xs)}.flex-1{flex:1}.animate-spin{animation:var(--animate-spin)}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.list-none{list-style-type:none}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1{gap:calc(var(--spacing)*1)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-6{gap:calc(var(--spacing)*6)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*5)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*5)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*8)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*8)*calc(1 - var(--tw-space-y-reverse)))}.gap-x-2{column-gap:calc(var(--spacing)*2)}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-brand-100>:not(:last-child)){border-color:var(--color-brand-100)}.overflow-hidden{overflow:hidden}.overflow-x-hidden{overflow-x:hidden}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-4{border-style:var(--tw-border-style);border-width:4px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-action-green-500{border-color:var(--color-action-green-500)}.border-action-red-500{border-color:var(--color-action-red-500)}.border-brand-100{border-color:var(--color-brand-100)}.border-brand-200{border-color:var(--color-brand-200)}.border-brand-300{border-color:var(--color-brand-300)}.border-brand-400{border-color:var(--color-brand-400)}.border-emerald-200{border-color:var(--color-emerald-200)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-t-brand-600{border-top-color:var(--color-brand-600)}.bg-action-green-50{background-color:var(--color-action-green-50)}.bg-action-green-500{background-color:var(--color-action-green-500)}.bg-action-red-50{background-color:var(--color-action-red-50)}.bg-brand-50,.bg-brand-50\/40{background-color:var(--color-brand-50)}@supports (color:color-mix(in lab, red, red)){.bg-brand-50\/40{background-color:color-mix(in oklab,var(--color-brand-50)40%,transparent)}}.bg-brand-50\/60{background-color:var(--color-brand-50)}@supports (color:color-mix(in lab, red, red)){.bg-brand-50\/60{background-color:color-mix(in oklab,var(--color-brand-50)60%,transparent)}}.bg-brand-50\/70{background-color:var(--color-brand-50)}@supports (color:color-mix(in lab, red, red)){.bg-brand-50\/70{background-color:color-mix(in oklab,var(--color-brand-50)70%,transparent)}}.bg-brand-50\/80{background-color:var(--color-brand-50)}@supports (color:color-mix(in lab, red, red)){.bg-brand-50\/80{background-color:color-mix(in oklab,var(--color-brand-50)80%,transparent)}}.bg-brand-100{background-color:var(--color-brand-100)}.bg-brand-500{background-color:var(--color-brand-500)}.bg-brand-600{background-color:var(--color-brand-600)}.bg-emerald-50{background-color:var(--color-emerald-50)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-white{background-color:var(--color-white)}.bg-white\/60{background-color:#fff9}@supports (color:color-mix(in lab, red, red)){.bg-white\/60{background-color:color-mix(in oklab,var(--color-white)60%,transparent)}}.bg-white\/88{background-color:#ffffffe0}@supports (color:color-mix(in lab, red, red)){.bg-white\/88{background-color:color-mix(in oklab,var(--color-white)88%,transparent)}}.bg-white\/90{background-color:#ffffffe6}@supports (color:color-mix(in lab, red, red)){.bg-white\/90{background-color:color-mix(in oklab,var(--color-white)90%,transparent)}}.bg-white\/95{background-color:#fffffff2}@supports (color:color-mix(in lab, red, red)){.bg-white\/95{background-color:color-mix(in oklab,var(--color-white)95%,transparent)}}.bg-gradient-to-b{--tw-gradient-position:to bottom in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-brand-50{--tw-gradient-from:var(--color-brand-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-brand-50\/80{--tw-gradient-from:var(--color-brand-50)}@supports (color:color-mix(in lab, red, red)){.from-brand-50\/80{--tw-gradient-from:color-mix(in oklab,var(--color-brand-50)80%,transparent)}}.from-brand-50\/80{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-white\/72{--tw-gradient-from:#ffffffb8}@supports (color:color-mix(in lab, red, red)){.from-white\/72{--tw-gradient-from:color-mix(in oklab,var(--color-white)72%,transparent)}}.from-white\/72{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.via-white{--tw-gradient-via:var(--color-white);--tw-gradient-via-stops:var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-via)var(--tw-gradient-via-position),var(--tw-gradient-to)var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-white\/62{--tw-gradient-via:#ffffff9e}@supports (color:color-mix(in lab, red, red)){.via-white\/62{--tw-gradient-via:color-mix(in oklab,var(--color-white)62%,transparent)}}.via-white\/62{--tw-gradient-via-stops:var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-via)var(--tw-gradient-via-position),var(--tw-gradient-to)var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.to-brand-50\/80{--tw-gradient-to:var(--color-brand-50)}@supports (color:color-mix(in lab, red, red)){.to-brand-50\/80{--tw-gradient-to:color-mix(in oklab,var(--color-brand-50)80%,transparent)}}.to-brand-50\/80{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-white{--tw-gradient-to:var(--color-white);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-white\/78{--tw-gradient-to:#ffffffc7}@supports (color:color-mix(in lab, red, red)){.to-white\/78{--tw-gradient-to:color-mix(in oklab,var(--color-white)78%,transparent)}}.to-white\/78{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.object-cover{object-fit:cover}.p-1{padding:calc(var(--spacing)*1)}.p-2{padding:calc(var(--spacing)*2)}.p-4{padding:calc(var(--spacing)*4)}.p-5{padding:calc(var(--spacing)*5)}.p-6{padding:calc(var(--spacing)*6)}.p-8{padding:calc(var(--spacing)*8)}.p-10{padding:calc(var(--spacing)*10)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-5{padding-inline:calc(var(--spacing)*5)}.px-8{padding-inline:calc(var(--spacing)*8)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-2\.5{padding-block:calc(var(--spacing)*2.5)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-6{padding-block:calc(var(--spacing)*6)}.py-8{padding-block:calc(var(--spacing)*8)}.py-10{padding-block:calc(var(--spacing)*10)}.pt-1{padding-top:calc(var(--spacing)*1)}.pt-2{padding-top:calc(var(--spacing)*2)}.pt-8{padding-top:calc(var(--spacing)*8)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.pb-6{padding-bottom:calc(var(--spacing)*6)}.pl-5{padding-left:calc(var(--spacing)*5)}.pl-6{padding-left:calc(var(--spacing)*6)}.text-center{text-align:center}.text-left{text-align:left}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-none{--tw-leading:1;line-height:1}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.text-pretty{text-wrap:pretty}.whitespace-pre-line{white-space:pre-line}.text-action-green-700{color:var(--color-action-green-700)}.text-action-red-600{color:var(--color-action-red-600)}.text-action-red-700{color:var(--color-action-red-700)}.text-amber-500{color:var(--color-amber-500)}.text-brand-600{color:var(--color-brand-600)}.text-brand-700{color:var(--color-brand-700)}.text-brand-800{color:var(--color-brand-800)}.text-brand-900{color:var(--color-brand-900)}.text-emerald-700{color:var(--color-emerald-700)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-900{color:var(--color-gray-900)}.text-green-700{color:var(--color-green-700)}.text-ink-500{color:var(--color-ink-500)}.text-ink-600{color:var(--color-ink-600)}.text-ink-700{color:var(--color-ink-700)}.text-ink-900{color:var(--color-ink-900)}.text-red-700{color:var(--color-red-700)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.underline{text-decoration-line:underline}.decoration-brand-300{-webkit-text-decoration-color:var(--color-brand-300);-webkit-text-decoration-color:var(--color-brand-300);text-decoration-color:var(--color-brand-300)}.underline-offset-2{text-underline-offset:2px}.underline-offset-4{text-underline-offset:4px}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.placeholder-gray-400::placeholder{color:var(--color-gray-400)}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.backdrop-blur-\[2px\]{--tw-backdrop-blur:blur(2px);-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.peer-checked\/dismiss\:border-action-red-700:is(:where(.peer\/dismiss):checked~*){border-color:var(--color-action-red-700)}.peer-checked\/dismiss\:bg-action-red-600:is(:where(.peer\/dismiss):checked~*){background-color:var(--color-action-red-600)}.peer-checked\/dismiss\:text-white:is(:where(.peer\/dismiss):checked~*){color:var(--color-white)}.peer-checked\/instructions\:block:is(:where(.peer\/instructions):checked~*){display:block}.peer-checked\/save\:border-action-green-700:is(:where(.peer\/save):checked~*){border-color:var(--color-action-green-700)}.peer-checked\/save\:bg-action-green-600:is(:where(.peer\/save):checked~*){background-color:var(--color-action-green-600)}.peer-checked\/save\:text-white:is(:where(.peer\/save):checked~*){color:var(--color-white)}@media (hover:hover){.hover\:border-brand-400:hover{border-color:var(--color-brand-400)}.hover\:bg-action-green-100:hover{background-color:var(--color-action-green-100)}.hover\:bg-action-green-600:hover{background-color:var(--color-action-green-600)}.hover\:bg-action-red-50:hover{background-color:var(--color-action-red-50)}.hover\:bg-action-red-100:hover{background-color:var(--color-action-red-100)}.hover\:bg-brand-50:hover{background-color:var(--color-brand-50)}.hover\:bg-brand-100:hover{background-color:var(--color-brand-100)}.hover\:bg-brand-200:hover{background-color:var(--color-brand-200)}.hover\:bg-brand-600:hover{background-color:var(--color-brand-600)}.hover\:bg-brand-700:hover{background-color:var(--color-brand-700)}.hover\:bg-white\/70:hover{background-color:#ffffffb3}@supports (color:color-mix(in lab, red, red)){.hover\:bg-white\/70:hover{background-color:color-mix(in oklab,var(--color-white)70%,transparent)}}.hover\:text-amber-500:hover{color:var(--color-amber-500)}.hover\:text-brand-600:hover{color:var(--color-brand-600)}.hover\:text-brand-700:hover{color:var(--color-brand-700)}.hover\:text-brand-800:hover{color:var(--color-brand-800)}.hover\:underline:hover{text-decoration-line:underline}}.focus\:border-brand-400:focus{border-color:var(--color-brand-400)}.focus\:border-brand-500:focus{border-color:var(--color-brand-500)}.focus\:bg-brand-50:focus{background-color:var(--color-brand-50)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-action-green-300:focus{--tw-ring-color:var(--color-action-green-300)}.focus\:ring-brand-300:focus{--tw-ring-color:var(--color-brand-300)}.focus\:ring-brand-400:focus{--tw-ring-color:var(--color-brand-400)}.focus\:ring-gray-300:focus{--tw-ring-color:var(--color-gray-300)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-brand-400:disabled{background-color:var(--color-brand-400)}.disabled\:text-brand-400:disabled{color:var(--color-brand-400)}.disabled\:text-white\/90:disabled{color:#ffffffe6}@supports (color:color-mix(in lab, red, red)){.disabled\:text-white\/90:disabled{color:color-mix(in oklab,var(--color-white)90%,transparent)}}@media (min-width:40rem){.sm\:w-40{width:calc(var(--spacing)*40)}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:items-end{align-items:flex-end}.sm\:justify-between{justify-content:space-between}.sm\:justify-end{justify-content:flex-end}.sm\:py-10{padding-block:calc(var(--spacing)*10)}}@media (min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:64rem){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}}@supports (color:oklch(62% 0.12 220)){:root{--color-ink-500:oklch(55% .032 248);--color-ink-600:oklch(46% .034 248);--color-ink-700:oklch(38.5% .035 250);--color-ink-900:oklch(24% .033 254);--color-action-green-50:oklch(97.6% .02 154);--color-action-green-100:oklch(94.5% .042 154);--color-action-green-300:oklch(82.8% .111 154);--color-action-green-500:oklch(68.5% .169 154);--color-action-green-600:oklch(58.8% .158 154);--color-action-green-700:oklch(50% .134 154);--color-action-red-50:oklch(97.6% .02 28);--color-action-red-100:oklch(94.5% .042 28);--color-action-red-300:oklch(82.8% .111 28);--color-action-red-500:oklch(68.5% .169 28);--color-action-red-600:oklch(58.8% .158 28);--color-action-red-700:oklch(50% .134 28)}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}
+36 -9
internal/templates/shoppinglist.html
··· 184 184 {{end}} 185 185 </div> 186 186 187 - <div class="mt-10 flex flex-wrap items-center gap-4"> 188 - <button type="button" 189 - id="finalizeButton" 190 - hx-post="/recipes/{{.Hash}}/finalize" 191 - hx-swap="none" 192 - class="inline-flex items-center justify-center rounded-lg bg-action-green-500 px-4 py-2.5 text-sm font-semibold text-white shadow-md transition hover:bg-action-green-600 focus:outline-none focus:ring-2 focus:ring-action-green-300 focus:ring-offset-2"> 193 - Assemble Shopping List 194 - </button> 195 - </div> 187 + {{template "shopping_finalize_controls" .}} 196 188 </div> 197 189 </div> 198 190 ··· 221 213 {{template "clerk_refresh.html" .}} 222 214 </body> 223 215 </html> 216 + 217 + {{define "shopping_finalize_controls"}} 218 + <div id="shopping-finalize-controls" class="mt-10 flex flex-wrap items-center gap-4"> 219 + {{template "shopping_finalize_controls_content" .}} 220 + </div> 221 + {{end}} 222 + 223 + {{define "shopping_finalize_controls_oob"}} 224 + <div id="shopping-finalize-controls" hx-swap-oob="outerHTML" class="mt-10 flex flex-wrap items-center gap-4"> 225 + {{template "shopping_finalize_controls_content" .}} 226 + </div> 227 + {{end}} 228 + 229 + {{define "shopping_finalize_controls_content"}} 230 + <span class="inline-flex" {{if not .HasSavedRecipes}}title="Save at least one recipe to assemble your shopping list."{{end}}> 231 + <button type="button" 232 + id="finalizeButton" 233 + {{if .HasSavedRecipes}}hx-post="/recipes/{{.Hash}}/finalize" 234 + hx-swap="none"{{else}}disabled 235 + aria-disabled="true" 236 + aria-describedby="finalize-help"{{end}} 237 + class="inline-flex items-center justify-center rounded-lg px-4 py-2.5 text-sm font-semibold shadow-md transition focus:outline-none focus:ring-2 focus:ring-offset-2 {{if .HasSavedRecipes}}bg-action-green-500 text-white hover:bg-action-green-600 focus:ring-action-green-300{{else}}cursor-not-allowed bg-gray-200 text-gray-500 shadow-none focus:ring-gray-300{{end}}"> 238 + Assemble Shopping List 239 + </button> 240 + </span> 241 + {{if not .HasSavedRecipes}} 242 + <p id="finalize-help" class="text-sm text-ink-600"> 243 + Save at least one recipe to assemble your shopping list. 244 + </p> 245 + {{end}} 246 + {{end}} 247 + 248 + {{define "shopping_finalize_controls_response"}} 249 + {{template "shopping_finalize_controls_oob" .}} 250 + {{end}} 224 251 225 252 {{define "shopping_recipe_wine_action"}} 226 253 <div id="{{.Wine.ActionID}}" class="shopping-wine-action">