ai cooking
0
fork

Configure Feed

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

rob feedback to codex (#259)

* rob feedback to codex

* this seems to be working

* make finalize and share recipes stick too

* Revert "make finalize and share recipes stick too"

This reverts commit fe709853543ce8c3f5a5ad39e6e708b1dd837672.

* fail gracefully

---------

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

authored by

Paul Miller
paul miller
and committed by
GitHub
9f85e210 b0e4e7b1

+259 -127
+1
AGENTS.md
··· 27 27 - Exported identifiers in `CamelCase`; package-private helpers in `lowerCamel`. Template names mirror file names in `internal/templates`. 28 28 - Prefer standard library first; add dependencies sparingly and record rationale in PR description if new. 29 29 - Prefer simple html to javascript frameworks 30 + - For UI copy, prefer plain culinary language over technical terms (example: use "Try again, chef" instead of "Regenerate", and "make it vegetarian" instead of "prefer vegetarian"). 30 31 31 32 ## Testing Guidelines 32 33 - Always run tests after making code changes. Default to `go test ./...`; use a narrower `go test ./... -run TestName` only when appropriate for quick iteration. If you cannot run tests, explicitly say why.
+22 -9
cmd/careme/web.go
··· 90 90 91 91 mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 92 92 ctx := r.Context() 93 - var currentUser *utypes.User 94 93 currentUser, err := userStorage.FromRequest(ctx, r, authClient) 95 94 if err != nil { 96 95 if !errors.Is(err, auth.ErrNoSession) { ··· 102 101 // just have two different templates? 103 102 104 103 } 104 + 105 + var favoriteStoreName string 106 + if currentUser != nil && currentUser.FavoriteStore != "" { 107 + loc, locErr := locationStorage.GetLocationByID(ctx, currentUser.FavoriteStore) 108 + if locErr != nil { 109 + slog.ErrorContext(ctx, "failed to get location name for favorite store", "location_id", currentUser.FavoriteStore, "error", locErr) 110 + //mutation intentionally not saved bac. 111 + currentUser.FavoriteStore = "" 112 + } else { 113 + favoriteStoreName = loc.Name 114 + } 115 + } 105 116 data := struct { 106 - ClarityScript template.HTML 107 - User *utypes.User 108 - Style seasons.Style 109 - ServerSignedIn bool 117 + ClarityScript template.HTML 118 + User *utypes.User 119 + FavoriteStoreName string 120 + Style seasons.Style 121 + ServerSignedIn bool 110 122 }{ 111 - ClarityScript: templates.ClarityScript(), 112 - User: currentUser, 113 - Style: seasons.GetCurrentStyle(), 114 - ServerSignedIn: currentUser != nil, 123 + ClarityScript: templates.ClarityScript(), 124 + User: currentUser, 125 + FavoriteStoreName: favoriteStoreName, 126 + Style: seasons.GetCurrentStyle(), 127 + ServerSignedIn: currentUser != nil, 115 128 } 116 129 if err := templates.Home.Execute(w, data); err != nil { 117 130 slog.ErrorContext(ctx, "home template execute error", "error", err)
+3 -3
internal/recipes/buttons_test.go
··· 70 70 t.Error("HTML should contain Details button text") 71 71 } 72 72 73 - // Check that "Regenerate" button exists 74 - if !strings.Contains(html, `Regenerate`) { 75 - t.Error("HTML should contain Regenerate button") 73 + // Check that "Try again, chef" button exists 74 + if !strings.Contains(html, `Try again, chef`) { 75 + t.Error("HTML should contain Try again, chef button") 76 76 } 77 77 78 78 // Check that "Finalize" button exists
+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-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--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-100:oklch(95% .052 163.051);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-600:oklch(59.6% .145 163.225);--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;--radius-3xl:1.5rem;--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)}}@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}}@layer components;@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}.inset-0{inset:calc(var(--spacing)*0)}.right-0{right:calc(var(--spacing)*0)}.z-10{z-index:10}.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}.items-center{align-items:center}.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)))}: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-3xl{border-radius:var(--radius-3xl)}.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-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-emerald-500{border-color:var(--color-emerald-500)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-red-500{border-color:var(--color-red-500)}.border-t-brand-600{border-top-color:var(--color-brand-600)}.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-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-emerald-500{background-color:var(--color-emerald-500)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-red-50{background-color:var(--color-red-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\/78{background-color:#ffffffc7}@supports (color:color-mix(in lab, red, red)){.bg-white\/78{background-color:color-mix(in oklab,var(--color-white)78%,transparent)}}.bg-white\/86{background-color:#ffffffdb}@supports (color:color-mix(in lab, red, red)){.bg-white\/86{background-color:color-mix(in oklab,var(--color-white)86%,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\/56{--tw-gradient-from:#ffffff8f}@supports (color:color-mix(in lab, red, red)){.from-white\/56{--tw-gradient-from:color-mix(in oklab,var(--color-white)56%,transparent)}}.from-white\/56{--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\/50{--tw-gradient-via:#ffffff80}@supports (color:color-mix(in lab, red, red)){.via-white\/50{--tw-gradient-via:color-mix(in oklab,var(--color-white)50%,transparent)}}.via-white\/50{--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\/68{--tw-gradient-to:#ffffffad}@supports (color:color-mix(in lab, red, red)){.to-white\/68{--tw-gradient-to:color-mix(in oklab,var(--color-white)68%,transparent)}}.to-white\/68{--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-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)}.py-16{padding-block:calc(var(--spacing)*16)}.pt-8{padding-top:calc(var(--spacing)*8)}.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)}.whitespace-pre-line{white-space:pre-line}.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-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-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.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-red-700:is(:where(.peer\/dismiss):checked~*){border-color:var(--color-red-700)}.peer-checked\/dismiss\:bg-red-600:is(:where(.peer\/dismiss):checked~*){background-color:var(--color-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-emerald-700:is(:where(.peer\/save):checked~*){border-color:var(--color-emerald-700)}.peer-checked\/save\:bg-emerald-600:is(:where(.peer\/save):checked~*){background-color:var(--color-emerald-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-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-emerald-100:hover{background-color:var(--color-emerald-100)}.hover\:bg-emerald-600:hover{background-color:var(--color-emerald-600)}.hover\:bg-red-50:hover{background-color:var(--color-red-50)}.hover\:bg-red-100:hover{background-color:var(--color-red-100)}.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\: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-brand-300:focus{--tw-ring-color:var(--color-brand-300)}.focus\:ring-brand-400:focus{--tw-ring-color:var(--color-brand-400)}.focus\:ring-emerald-300:focus{--tw-ring-color:var(--color-emerald-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\:w-48{width:calc(var(--spacing)*48)}.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}}@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))}}}@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-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}}@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-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)))}: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\/56{--tw-gradient-from:#ffffff8f}@supports (color:color-mix(in lab, red, red)){.from-white\/56{--tw-gradient-from:color-mix(in oklab,var(--color-white)56%,transparent)}}.from-white\/56{--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\/50{--tw-gradient-via:#ffffff80}@supports (color:color-mix(in lab, red, red)){.via-white\/50{--tw-gradient-via:color-mix(in oklab,var(--color-white)50%,transparent)}}.via-white\/50{--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\/68{--tw-gradient-to:#ffffffad}@supports (color:color-mix(in lab, red, red)){.to-white\/68{--tw-gradient-to:color-mix(in oklab,var(--color-white)68%,transparent)}}.to-white\/68{--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-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-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-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\: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)}}
+71 -72
internal/templates/home.html
··· 9 9 10 10 {{.ClarityScript}} 11 11 </head> 12 - <body class="relative min-h-screen overflow-x-hidden bg-gradient-to-b from-brand-50/80 via-white to-brand-50/80 antialiased"> 12 + <body class="relative min-h-screen overflow-x-hidden bg-gradient-to-b from-brand-50/80 via-white to-brand-50/80 text-ink-700 antialiased"> 13 13 <div class="pointer-events-none absolute inset-0" aria-hidden="true"> 14 14 <img src="https://images.northbriton.net/cdn-cgi/image/width=800/https://images.northbriton.net/AP1GczODcc6U8rXC6v9NcbUhHuYRa3JYqRxZsn6flSVCwfGL6_C1BNrzFpwfzaj29hg7QVFOomYxLoskLWJnHcBZqHe4FhDg83JFSGYPycvvTMCbgdBugu58" alt="" class="absolute inset-0 h-full w-full object-cover" style="opacity: 0.42;" /> 15 15 <div class="absolute inset-0 bg-gradient-to-b from-white/56 via-white/50 to-white/68"></div> 16 16 </div> 17 17 18 - <main class="relative px-4 py-10"> 18 + <main class="relative px-4 py-8 sm:py-10"> 19 19 <section class="mx-auto w-full max-w-3xl"> 20 - <div class="rounded-3xl border border-brand-100 bg-white/78 shadow-2xl backdrop-blur-[2px]"> 20 + <div class="friendly-card border border-brand-100 bg-white/88 shadow-2xl backdrop-blur-[2px]"> 21 21 <!-- Header --> 22 22 <div class="border-b border-brand-100 p-8"> 23 23 <div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"> 24 24 <div> 25 25 <h1 class="text-4xl font-extrabold tracking-tight text-brand-700">Careme</h1> 26 - <p class="mt-2 text-gray-600"> 26 + <p class="mt-2 text-ink-600"> 27 27 Your personal chef and sommelier. 28 28 </p> 29 29 </div> ··· 37 37 </svg> 38 38 </summary> 39 39 <div class="absolute right-0 z-10 mt-2 w-48 rounded-xl border border-gray-200 bg-white p-2 shadow-lg"> 40 - <a href="/user" class="block rounded-lg px-3 py-2 text-sm text-gray-700 transition hover:bg-brand-50">View profile</a> 40 + <a href="/user" class="block rounded-lg px-3 py-2 text-sm text-ink-700 transition hover:bg-brand-50">View profile</a> 41 41 <form method="POST" action="/logout"> 42 - <button type="submit" class="w-full rounded-lg px-3 py-2 text-left text-sm text-red-600 transition hover:bg-red-50">Sign out</button> 42 + <button type="submit" class="w-full rounded-lg px-3 py-2 text-left text-sm text-action-red-600 transition hover:bg-action-red-50">Sign out</button> 43 43 </form> 44 44 </div> 45 45 </details> ··· 50 50 51 51 <!-- Content --> 52 52 <div class="p-8"> 53 - <div class="rounded-2xl border border-brand-100 bg-white/86 p-6 shadow-sm"> 54 - {{if .User}} 55 - <div class="grid gap-6 lg:grid-cols-3"> 56 - <div class="rounded-xl border border-brand-100 bg-brand-50/40 p-5"> 57 - <h2 class="text-lg font-semibold text-brand-700">Find a store</h2> 58 - {{if .User.FavoriteStore}} 59 - <a href="/recipes?location={{.User.FavoriteStore}}" 60 - class="mt-4 inline-flex w-full items-center justify-center rounded-lg border border-brand-300 bg-white px-4 py-2.5 text-sm font-semibold text-brand-700 shadow-sm transition hover:bg-brand-50 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:ring-offset-2"> 61 - Favorite store 62 - </a> 63 - {{end}} 64 - <form method="GET" action="/locations" class="mt-4 space-y-2"> 65 - <label for="zip" class="block text-sm font-medium text-gray-700"> 66 - Enter ZIP code: 67 - </label> 68 - <div class="flex flex-col gap-3"> 69 - <input id="zip" name="zip" type="text" inputmode="numeric" pattern="\d{5}" required 70 - placeholder="e.g. 90210" 71 - class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-gray-900 placeholder-gray-400 shadow-sm focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-400" /> 72 - <button type="submit" 73 - class="inline-flex w-full items-center justify-center rounded-lg bg-brand-500 px-4 py-2.5 font-medium text-white shadow-md transition hover:bg-brand-600 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:ring-offset-2"> 74 - Find stores 75 - </button> 76 - </div> 77 - </form> 78 - </div> 79 - 80 - <div class="rounded-xl border border-brand-100 bg-brand-50/40 p-5"> 81 - <h2 class="text-lg font-semibold text-brand-700">Your kitchen</h2> 82 - <div class="mt-4 flex flex-wrap gap-3"> 83 - <a href="/user?tab=past" 84 - class="inline-flex items-center justify-center rounded-lg bg-brand-600 px-4 py-2.5 text-sm font-semibold text-white shadow-md transition hover:bg-brand-700 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:ring-offset-2"> 85 - Past recipes 86 - </a> 87 - <a href="/user?tab=customize" 88 - class="inline-flex items-center justify-center rounded-lg border border-brand-300 bg-white px-4 py-2.5 text-sm font-semibold text-brand-700 shadow-sm transition hover:bg-brand-50 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:ring-offset-2"> 89 - Customize 90 - </a> 53 + {{if .User}} 54 + <div class="grid gap-6 lg:grid-cols-3"> 55 + <div class="friendly-card-soft border border-brand-100 bg-brand-50/40 p-5"> 56 + <h2 class="text-lg font-semibold text-brand-700">Find a store</h2> 57 + {{if .User.FavoriteStore}} 58 + <a href="/recipes?location={{.User.FavoriteStore}}" 59 + class="mt-4 inline-flex w-full flex-col items-start gap-1 rounded-lg border border-brand-300 bg-white px-4 py-2.5 shadow-sm transition hover:bg-brand-50 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:ring-offset-2"> 60 + <span class="text-xs font-semibold uppercase tracking-wide text-ink-500">Favorite store</span> 61 + <span class="text-sm font-semibold text-brand-700 text-pretty">{{if .FavoriteStoreName}}{{.FavoriteStoreName}}{{else}}{{.User.FavoriteStore}}{{end}}</span> 62 + </a> 63 + {{end}} 64 + <form method="GET" action="/locations" class="mt-4 space-y-2"> 65 + <label for="zip" class="block text-sm font-medium text-ink-700"> 66 + Enter ZIP code: 67 + </label> 68 + <div class="flex flex-col gap-3"> 69 + <input id="zip" name="zip" type="text" inputmode="numeric" pattern="\d{5}" required 70 + placeholder="e.g. 90210" 71 + class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-ink-900 placeholder-gray-400 shadow-sm focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-400" /> 72 + <button type="submit" 73 + class="inline-flex w-full items-center justify-center rounded-lg bg-brand-500 px-4 py-2.5 font-medium text-white shadow-md transition hover:bg-brand-600 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:ring-offset-2"> 74 + Find stores 75 + </button> 91 76 </div> 92 - </div> 77 + </form> 78 + </div> 93 79 94 - <div class="rounded-xl border border-brand-100 bg-brand-50/40 p-5"> 95 - <a href="/about" 96 - class="inline-flex w-full items-center justify-center rounded-lg border-2 border-brand-300 bg-white/90 px-4 py-2.5 text-sm font-semibold tracking-wide text-brand-700 shadow-sm transition hover:border-brand-400 hover:bg-brand-50 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:ring-offset-2"> 97 - About Careme 80 + <div class="friendly-card-soft border border-brand-100 bg-brand-50/40 p-5"> 81 + <h2 class="text-lg font-semibold text-brand-700">Your kitchen</h2> 82 + <div class="mt-4 flex flex-wrap gap-3"> 83 + <a href="/user?tab=past" 84 + class="inline-flex items-center justify-center rounded-lg bg-brand-600 px-4 py-2.5 text-sm font-semibold text-white shadow-md transition hover:bg-brand-700 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:ring-offset-2"> 85 + Past recipes 86 + </a> 87 + <a href="/user?tab=customize" 88 + class="inline-flex items-center justify-center rounded-lg border border-brand-300 bg-white px-4 py-2.5 text-sm font-semibold text-brand-700 shadow-sm transition hover:bg-brand-50 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:ring-offset-2"> 89 + Customize 98 90 </a> 99 91 </div> 100 92 </div> 101 - {{else}} 102 - <p class="text-gray-700 whitespace-pre-line">Careme will: 93 + 94 + <div class="friendly-card-soft border border-brand-100 bg-brand-50/40 p-5"> 95 + <a href="/about" 96 + class="inline-flex w-full items-center justify-center rounded-lg border-2 border-brand-300 bg-white/90 px-4 py-2.5 text-sm font-semibold tracking-wide text-brand-700 shadow-sm transition hover:border-brand-400 hover:bg-brand-50 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:ring-offset-2"> 97 + About Careme 98 + </a> 99 + </div> 100 + </div> 101 + {{else}} 102 + <p class="whitespace-pre-line text-ink-700">Careme will: 103 103 104 104 Find your local grocery stores. 105 105 Check the store's inventory for fresh meat and seasonal produce. 106 106 Generate a weekly meal plan from a variety of cuisines and cooking styles. 107 107 </p> 108 - <!-- Signed-out state --> 109 - <div class="mt-6"> 110 - <p class="block text-sm font-medium text-gray-700 mb-3"> 111 - Sign in or create an account to continue: 112 - </p> 113 - <div class="flex flex-col gap-3 sm:flex-row"> 114 - <a href="/sign-in" 115 - class="inline-flex items-center justify-center rounded-lg bg-brand-600 px-4 py-2.5 font-semibold text-white shadow-md transition hover:bg-brand-700 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:ring-offset-2"> 116 - Sign In 117 - </a> 118 - <a href="/sign-up" 119 - class="inline-flex items-center justify-center rounded-lg border border-brand-300 bg-white px-4 py-2.5 font-semibold text-brand-700 shadow-sm transition hover:bg-brand-50 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:ring-offset-2"> 120 - Sign Up 121 - </a> 122 - <a href="/about" 123 - class="inline-flex items-center justify-center rounded-lg border-2 border-brand-300 bg-white/90 px-4 py-2.5 font-semibold text-brand-700 shadow-sm transition hover:border-brand-400 hover:bg-brand-50 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:ring-offset-2"> 124 - About Careme 125 - </a> 126 - </div> 108 + <!-- Signed-out state --> 109 + <div class="mt-6"> 110 + <p class="mb-3 block text-sm font-medium text-ink-700"> 111 + Sign in or create an account to continue: 112 + </p> 113 + <div class="flex flex-col gap-3 sm:flex-row"> 114 + <a href="/sign-in" 115 + class="inline-flex items-center justify-center rounded-lg bg-brand-600 px-4 py-2.5 font-semibold text-white shadow-md transition hover:bg-brand-700 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:ring-offset-2"> 116 + Sign In 117 + </a> 118 + <a href="/sign-up" 119 + class="inline-flex items-center justify-center rounded-lg border border-brand-300 bg-white px-4 py-2.5 font-semibold text-brand-700 shadow-sm transition hover:bg-brand-50 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:ring-offset-2"> 120 + Sign Up 121 + </a> 122 + <a href="/about" 123 + class="inline-flex items-center justify-center rounded-lg border-2 border-brand-300 bg-white/90 px-4 py-2.5 font-semibold text-brand-700 shadow-sm transition hover:border-brand-400 hover:bg-brand-50 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:ring-offset-2"> 124 + About Careme 125 + </a> 127 126 </div> 128 - {{end}} 129 127 </div> 128 + {{end}} 130 129 </div> 131 130 </div> 132 131 133 132 <!-- Footer note --> 134 - <p class="mt-6 text-center text-sm text-gray-500"> 133 + <p class="mt-6 text-center text-sm text-ink-500 text-pretty"> 135 134 By continuing you agree to our terms. Fresh picks, zero spam. 136 135 </p> 137 136 </section>
+33 -31
internal/templates/shoppinglist.html
··· 24 24 25 25 {{.ClarityScript}} 26 26 </head> 27 - <body class="min-h-screen bg-gradient-to-b from-brand-50 to-white antialiased"> 27 + <body class="min-h-screen bg-gradient-to-b from-brand-50 to-white text-ink-700 antialiased"> 28 28 <main class="px-4 py-10"> 29 29 <section class="mx-auto w-full max-w-5xl space-y-8"> 30 - <div class="rounded-2xl border border-brand-100 bg-white/90 shadow-xl"> 30 + <div class="friendly-card border border-brand-100 bg-white/90 shadow-xl"> 31 31 <div class="border-b border-brand-100 p-8"> 32 32 <h1 class="text-4xl font-extrabold tracking-tight text-brand-700"> 33 33 <a href="/" class="hover:text-brand-600 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:ring-offset-2">Careme Recipes</a> 34 34 </h1> 35 - <p class="mt-2 text-gray-600"> 35 + <p class="mt-2 text-ink-600"> 36 36 Location: <span class="font-semibold text-brand-700">{{.Location.Name}}</span> 37 - <span class="text-sm text-gray-500">({{.Location.Address}})</span> 37 + <span class="text-sm text-ink-500">({{.Location.Address}})</span> 38 38 </p> 39 39 </div> 40 40 ··· 44 44 <input type="hidden" name="date" value="{{.Date}}" /> 45 45 <input type="hidden" name="conversation_id" value="{{.ConversationID}}" /> 46 46 47 - <div class="flex flex-col gap-3 rounded-xl border border-brand-100 bg-brand-50/60 p-4 sm:flex-row sm:items-center"> 48 - <label for="instructions" class="text-sm font-medium text-gray-700 sm:w-48">Extra instructions</label> 49 - <input id="instructions" 50 - type="text" 51 - name="instructions" 52 - value="{{.Instructions}}" 53 - placeholder="e.g. prefer vegetarian" 54 - class="w-full flex-1 rounded-lg border border-gray-300 bg-white px-3 py-2 text-gray-900 shadow-sm focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-400" /> 55 - <button type="submit" 56 - class="inline-flex items-center justify-center rounded-lg bg-brand-600 px-4 py-2.5 text-sm font-semibold text-white shadow-md transition hover:bg-brand-700 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:ring-offset-2"> 57 - Regenerate 58 - </button> 47 + <div class="sticky top-3 z-20 -mx-2 px-2 pb-4"> 48 + <div class="friendly-card-soft flex flex-col gap-3 border border-brand-100 bg-brand-50/80 p-4 shadow-sm backdrop-blur-[2px] sm:flex-row sm:items-center"> 49 + <label for="instructions" class="text-sm font-medium text-ink-700 sm:w-40">Chef notes</label> 50 + <input id="instructions" 51 + type="text" 52 + name="instructions" 53 + value="{{.Instructions}}" 54 + placeholder="e.g. make it vegetarian" 55 + class="w-full flex-1 rounded-lg border border-gray-300 bg-white px-3 py-2 text-ink-900 shadow-sm focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-400" /> 56 + <button type="submit" 57 + class="inline-flex items-center justify-center rounded-lg bg-brand-600 px-4 py-2.5 text-sm font-semibold text-white shadow-md transition hover:bg-brand-700 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:ring-offset-2"> 58 + Try again, chef 59 + </button> 60 + </div> 59 61 </div> 60 62 61 63 <div class="mt-8 space-y-8"> ··· 66 68 <a href="/recipe/{{.ComputeHash}}" class="text-2xl font-semibold text-brand-700 hover:text-brand-600 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:ring-offset-2"> 67 69 {{.Title}} 68 70 </a> 69 - <p class="mt-1 text-sm text-gray-500">{{.Description}}</p> 71 + <p class="mt-1 text-sm text-ink-500">{{.Description}}</p> 70 72 </div> 71 73 <div class="flex flex-wrap items-center gap-3"> 72 74 <input type="radio" {{if .Saved}}checked{{end}} name="recipe-{{.ComputeHash}}" value="save" id="save-{{.ComputeHash}}" class="peer/save hidden" onchange="document.getElementById('saved-{{.ComputeHash}}').value = '{{.ComputeHash}}'; document.getElementById('dismissed-{{.ComputeHash}}').value = ''; setRecipeDetailsVisible('{{.ComputeHash}}', false); updateFinalizeButton();" /> 73 - <label for="save-{{.ComputeHash}}" class="inline-flex items-center justify-center rounded-lg border-2 border-emerald-500 bg-emerald-50 px-4 py-2 text-sm font-medium text-emerald-700 cursor-pointer transition hover:bg-emerald-100 peer-checked/save:bg-emerald-600 peer-checked/save:text-white peer-checked/save:border-emerald-700"> 75 + <label for="save-{{.ComputeHash}}" class="inline-flex cursor-pointer items-center justify-center rounded-lg border-2 border-action-green-500 bg-action-green-50 px-4 py-2 text-sm font-medium text-action-green-700 transition hover:bg-action-green-100 peer-checked/save:border-action-green-700 peer-checked/save:bg-action-green-600 peer-checked/save:text-white"> 74 76 Save 75 77 </label> 76 78 <input type="radio" name="recipe-{{.ComputeHash}}" value="dismiss" id="dismiss-{{.ComputeHash}}" class="peer/dismiss hidden" onchange="document.getElementById('dismissed-{{.ComputeHash}}').value = '{{.ComputeHash}}'; document.getElementById('saved-{{.ComputeHash}}').value = ''; setRecipeDetailsVisible('{{.ComputeHash}}', false); updateFinalizeButton();" /> 77 - <label for="dismiss-{{.ComputeHash}}" class="inline-flex items-center justify-center rounded-lg border-2 border-red-500 bg-red-50 px-4 py-2 text-sm font-medium text-red-700 cursor-pointer transition hover:bg-red-100 peer-checked/dismiss:bg-red-600 peer-checked/dismiss:text-white peer-checked/dismiss:border-red-700"> 79 + <label for="dismiss-{{.ComputeHash}}" class="inline-flex cursor-pointer items-center justify-center rounded-lg border-2 border-action-red-500 bg-action-red-50 px-4 py-2 text-sm font-medium text-action-red-700 transition hover:bg-action-red-100 peer-checked/dismiss:border-action-red-700 peer-checked/dismiss:bg-action-red-600 peer-checked/dismiss:text-white"> 78 80 Dismiss 79 81 </label> 80 82 <button type="button" ··· 93 95 <section id="details-{{.ComputeHash}}" class="space-y-6 {{if .Saved}}hidden{{end}}"> 94 96 <div class="grid gap-6 md:grid-cols-2"> 95 97 <div> 96 - <h3 class="text-sm font-semibold uppercase tracking-wide text-gray-500">Ingredients</h3> 97 - <ul class="mt-3 space-y-2 text-gray-700"> 98 + <h3 class="text-sm font-semibold uppercase tracking-wide text-ink-500">Ingredients</h3> 99 + <ul class="mt-3 space-y-2 text-ink-700"> 98 100 {{range .Ingredients}} 99 101 <li class="flex flex-wrap items-center justify-between gap-2 rounded-lg bg-brand-50 px-3 py-2 text-sm"> 100 102 <span class="font-medium text-brand-700">{{.Name}}</span> 101 - <span class="text-gray-600">{{.Quantity}}</span> 102 - <span class="text-gray-500">{{.Price}}</span> 103 + <span class="text-ink-600">{{.Quantity}}</span> 104 + <span class="text-ink-500">{{.Price}}</span> 103 105 </li> 104 106 {{end}} 105 107 </ul> 106 108 </div> 107 109 <div> 108 - <h3 class="text-sm font-semibold uppercase tracking-wide text-gray-500">Instructions</h3> 109 - <ol class="mt-3 list-decimal space-y-2 pl-5 text-gray-700"> 110 + <h3 class="text-sm font-semibold uppercase tracking-wide text-ink-500">Instructions</h3> 111 + <ol class="mt-3 list-decimal space-y-2 pl-5 text-ink-700"> 110 112 {{range .Instructions}} 111 113 <li>{{.}}</li> 112 114 {{end}} ··· 114 116 </div> 115 117 </div> 116 118 117 - <div class="space-y-2 text-sm text-gray-600"> 119 + <div class="space-y-2 text-sm text-ink-600"> 118 120 <p><span class="font-semibold text-brand-700">Health notes:</span> {{.Health}}</p> 119 121 <p><span class="font-semibold text-brand-700">Drink pairing:</span> {{.DrinkPairing}}</p> 120 122 </div> ··· 135 137 </button> 136 138 </div> 137 139 <div id="shoppingListPanel" class="mt-4 hidden"> 138 - <ul class="space-y-2 text-gray-700"> 140 + <ul class="space-y-2 text-ink-700"> 139 141 {{range .ShoppingList}} 140 142 <li class="flex flex-wrap items-center justify-between gap-2 rounded-lg bg-brand-50 px-3 py-2 text-sm"> 141 143 <span class="font-medium text-brand-700">{{.Name}}</span> 142 - <span class="text-gray-600">{{.Quantity}}</span> 144 + <span class="text-ink-600">{{.Quantity}}</span> 143 145 </li> 144 146 {{end}} 145 147 </ul> ··· 151 153 152 154 <div class="mt-10 flex flex-wrap items-center gap-4"> 153 155 <button type="submit" form="regenerateForm" name="finalize" value="true" id="finalizeButton" 154 - class="inline-flex items-center justify-center rounded-lg bg-emerald-500 px-4 py-2.5 text-sm font-semibold text-white shadow-md transition hover:bg-emerald-600 focus:outline-none focus:ring-2 focus:ring-emerald-300 focus:ring-offset-2"> 156 + 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"> 155 157 Finalize 156 158 </button> 157 159 <button type="button" 158 160 onclick="shareRecipes()" 159 - class="inline-flex items-center justify-center rounded-lg bg-emerald-500 px-4 py-2.5 text-sm font-semibold text-white shadow-md transition hover:bg-emerald-600 focus:outline-none focus:ring-2 focus:ring-emerald-300 focus:ring-offset-2"> 161 + 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"> 160 162 Share Recipes 161 163 </button> 162 164 </div> 163 165 </div> 164 166 </div> 165 167 166 - <p class="text-center text-sm text-gray-500">Generated by Careme.</p> 168 + <p class="text-center text-sm text-ink-500">Generated by Careme.</p> 167 169 </section> 168 170 </main> 169 171
+4 -4
internal/templates/spinner.html
··· 12 12 13 13 {{.ClarityScript}} 14 14 </head> 15 - <body class="min-h-screen bg-gradient-to-b from-brand-50 to-white antialiased"> 16 - <main class="flex min-h-screen items-center justify-center px-4 py-16" role="status" aria-live="polite"> 17 - <section class="w-full max-w-md rounded-2xl border border-brand-100 bg-white/90 p-10 text-center shadow-xl"> 15 + <body class="min-h-screen overflow-hidden bg-gradient-to-b from-brand-50 to-white text-ink-700 antialiased"> 16 + <main class="grid min-h-screen place-items-center px-4" role="status" aria-live="polite"> 17 + <section class="friendly-card w-full max-w-md border border-brand-100 bg-white/90 p-10 text-center shadow-xl"> 18 18 <div class="mx-auto h-14 w-14 animate-spin rounded-full border-4 border-brand-100 border-t-brand-600" aria-hidden="true"></div> 19 19 <span class="sr-only">Loading</span> 20 20 <h1 class="mt-6 text-2xl font-semibold text-brand-700">Please wait…</h1> 21 - <p class="mt-2 text-sm text-gray-600">We’re generating your result. This page refreshes every {{.RefreshInterval}} seconds.</p> 21 + <p class="mt-2 text-sm text-ink-600">We’re generating your result. This page refreshes every {{.RefreshInterval}} seconds.</p> 22 22 <p class="mt-4 text-sm"> 23 23 <a class="font-medium text-brand-600 underline-offset-4 hover:underline focus:outline-none focus:ring-2 focus:ring-brand-400 focus:ring-offset-2" href="">Refresh now</a> 24 24 </p>
+10 -7
internal/users/server.go
··· 150 150 activeTab = "customize" 151 151 } 152 152 153 + userCopy := *currentUser 154 + userForTemplate := &userCopy 155 + 153 156 // Fetch location name if favorite store is set 154 157 var favoriteStoreName string 155 - if currentUser.FavoriteStore != "" && s.locGetter != nil { 156 - loc, err := s.locGetter.GetLocationByID(ctx, currentUser.FavoriteStore) 158 + if userForTemplate.FavoriteStore != "" && s.locGetter != nil { 159 + loc, err := s.locGetter.GetLocationByID(ctx, userForTemplate.FavoriteStore) 157 160 if err != nil { 158 - slog.WarnContext(ctx, "failed to get location name for favorite store", "location_id", currentUser.FavoriteStore, "error", err) 159 - favoriteStoreName = currentUser.FavoriteStore // fallback to ID 161 + slog.ErrorContext(ctx, "failed to get location name for favorite store", "location_id", userForTemplate.FavoriteStore, "error", err) 162 + userForTemplate.FavoriteStore = "" 160 163 } else { 161 164 favoriteStoreName = loc.Name 162 165 } 163 166 } 164 167 // TODO paginate and search on page instead. 165 - if len(currentUser.LastRecipes) > 14 { 166 - currentUser.LastRecipes = currentUser.LastRecipes[0:14] 168 + if len(userForTemplate.LastRecipes) > 14 { 169 + userForTemplate.LastRecipes = userForTemplate.LastRecipes[0:14] 167 170 } 168 171 data := struct { 169 172 ClarityScript template.HTML ··· 175 178 ServerSignedIn bool 176 179 }{ 177 180 ClarityScript: templates.ClarityScript(), 178 - User: currentUser, 181 + User: userForTemplate, 179 182 Success: success, 180 183 FavoriteStoreName: favoriteStoreName, 181 184 ActiveTab: activeTab,
+54
internal/users/server_test.go
··· 2 2 3 3 import ( 4 4 "careme/internal/cache" 5 + "careme/internal/locations" 5 6 utypes "careme/internal/users/types" 6 7 "context" 8 + "errors" 7 9 "html/template" 8 10 "net/http" 9 11 "net/http/httptest" ··· 29 31 } 30 32 31 33 func (t testAuthClient) Register(_ *http.ServeMux) {} 34 + 35 + type failingLocationGetter struct{} 36 + 37 + func (f failingLocationGetter) GetLocationByID(_ context.Context, _ string) (*locations.Location, error) { 38 + return nil, errors.New("lookup failed") 39 + } 32 40 33 41 func TestHandleUser_SavesDirective(t *testing.T) { 34 42 t.Parallel() ··· 104 112 t.Fatalf("expected generation prompt to be cleared, got %q", user.Directive) 105 113 } 106 114 } 115 + 116 + func TestHandleUser_BlanksFavoriteStoreInHTMLWhenLocationLookupFails(t *testing.T) { 117 + t.Parallel() 118 + cacheStore := cache.NewFileCache(filepath.Join(t.TempDir(), "cache")) 119 + storage := NewStorage(cacheStore) 120 + s := &server{ 121 + storage: storage, 122 + userTmpl: template.Must(template.New("user").Parse("favorite={{.User.FavoriteStore}} name={{.FavoriteStoreName}}")), 123 + locGetter: failingLocationGetter{}, 124 + clerk: testAuthClient{}, 125 + } 126 + 127 + existing := &utypes.User{ 128 + ID: "user-1", 129 + Email: []string{"user@example.com"}, 130 + CreatedAt: time.Now(), 131 + ShoppingDay: "Saturday", 132 + FavoriteStore: "70500874", 133 + } 134 + if err := storage.Update(existing); err != nil { 135 + t.Fatalf("failed to seed user: %v", err) 136 + } 137 + 138 + req := httptest.NewRequest(http.MethodGet, "/user", nil) 139 + rr := httptest.NewRecorder() 140 + s.handleUser(rr, req) 141 + 142 + if rr.Code != http.StatusOK { 143 + t.Fatalf("expected status %d, got %d", http.StatusOK, rr.Code) 144 + } 145 + body := rr.Body.String() 146 + if strings.Contains(body, "70500874") { 147 + t.Fatalf("expected favorite store to be blanked in template output, got %q", body) 148 + } 149 + if !strings.Contains(body, "favorite= name=") { 150 + t.Fatalf("expected favorite and favorite name to be blank in output, got %q", body) 151 + } 152 + 153 + user, err := storage.GetByID("user-1") 154 + if err != nil { 155 + t.Fatalf("expected user to remain stored, got error %v", err) 156 + } 157 + if user.FavoriteStore != "70500874" { 158 + t.Fatalf("expected persisted favorite store to stay unchanged, got %q", user.FavoriteStore) 159 + } 160 + }
+60
tailwind/input.css
··· 13 13 --color-brand-700: var(--brand-700); 14 14 --color-brand-800: var(--brand-800); 15 15 --color-brand-900: var(--brand-900); 16 + 17 + --color-ink-500: #64748b; 18 + --color-ink-600: #475569; 19 + --color-ink-700: #334155; 20 + --color-ink-900: #0f172a; 21 + 22 + --color-action-green-50: #ecfdf5; 23 + --color-action-green-100: #d1fae5; 24 + --color-action-green-300: #6ee7b7; 25 + --color-action-green-500: #10b981; 26 + --color-action-green-600: #059669; 27 + --color-action-green-700: #047857; 28 + 29 + --color-action-red-50: #fef2f2; 30 + --color-action-red-100: #fee2e2; 31 + --color-action-red-300: #fca5a5; 32 + --color-action-red-500: #ef4444; 33 + --color-action-red-600: #dc2626; 34 + --color-action-red-700: #b91c1c; 35 + } 36 + 37 + @layer base { 38 + p { 39 + text-wrap: pretty; 40 + } 41 + } 42 + 43 + @supports (color: oklch(62% 0.12 220)) { 44 + :root { 45 + --color-ink-500: oklch(55.0% 0.032 248); 46 + --color-ink-600: oklch(46.0% 0.034 248); 47 + --color-ink-700: oklch(38.5% 0.035 250); 48 + --color-ink-900: oklch(24.0% 0.033 254); 49 + 50 + --color-action-green-50: oklch(97.6% 0.020 154); 51 + --color-action-green-100: oklch(94.5% 0.042 154); 52 + --color-action-green-300: oklch(82.8% 0.111 154); 53 + --color-action-green-500: oklch(68.5% 0.169 154); 54 + --color-action-green-600: oklch(58.8% 0.158 154); 55 + --color-action-green-700: oklch(50.0% 0.134 154); 56 + 57 + --color-action-red-50: oklch(97.6% 0.020 28); 58 + --color-action-red-100: oklch(94.5% 0.042 28); 59 + --color-action-red-300: oklch(82.8% 0.111 28); 60 + --color-action-red-500: oklch(68.5% 0.169 28); 61 + --color-action-red-600: oklch(58.8% 0.158 28); 62 + --color-action-red-700: oklch(50.0% 0.134 28); 63 + } 64 + } 65 + 66 + @layer components { 67 + .friendly-card { 68 + border-radius: 48px; 69 + corner-shape: squircle; 70 + } 71 + 72 + .friendly-card-soft { 73 + border-radius: 28px; 74 + corner-shape: squircle; 75 + } 16 76 } 17 77 /* This seems like not the right way to do this. 18 78 https://tailwindcss.com/docs/adding-custom-variants#custom-variants