/* ==========================================================================
   COCAMOO — BASE
   Colours, type, the two shells (phone tab bar, desktop top bar and rails),
   footer, buttons and cards shared by every page.
   The design is round 7, "Clean Kitchen with the tactile kit"
   (docs/design/APP-DESIGN-HANDOFF.md, colours in docs/design/COLOUR-RULES.md):
   neutral greys, plum is the one "you can tap this" colour, depth comes from
   light (raised cards, pressed-in wells, glass), course colours are dots.
   ========================================================================== */

:root {
  /* palette */
  --ground: #F7F7F8;       /* the page background */
  --card: #FFFFFF;         /* cards, menus, sheets, raised buttons */
  --well-bg: rgba(24, 24, 27, .065); /* inside search boxes, inputs, segmented tracks: see-through,
                                       so a well is pressed into whatever it sits on (a white card
                                       = #EEEEF1, the ground, glass over a photo or a colour) */
  --sep: #F0F0F2;          /* lines between rows inside a card */
  --ink: #18181B;
  --muted: #52525B;
  --soft: #6B6B73;
  --faint: #A1A1AA;
  --line: #E4E4E7;
  --ring: #D4D4D8;
  --band: #F4F4F5;
  --white: #FFFFFF;        /* text on plum and on photos: stays white in dark mode */
  --plum: #9E2E57;
  --plum-dark: #7E2346;
  --plum-pressed: #8E2850;
  --plum-text: #9E2E57;    /* plum links and labels (white in dark mode) */
  --plum-icon: #9E2E57;    /* hearts and other plum icons (#C25A86 in dark mode) */
  --plum-tint: rgba(158, 46, 87, .08);
  --plum-glow: rgba(158, 46, 87, .16);
  --scrim: rgba(10, 10, 13, .32);   /* behind sheets */
  --error-button: #B42318;         /* Delete my account: white text on it */
  --paper: #FAF5EC;        /* the recipe ribbon only: top of its gradient */
  --paper-2: #F3EADC;
  --paper-tail: #C9B293;
  --note: #FCF6DF;         /* recipe notes and "needs a look" cards: top of the gradient */
  --note-2: #F9EFCB;
  /* the falling leaves only (Nov 11, from the Webflow site, leaves.js):
     reds, oranges, golds, then just-turned yellow-greens */
  --leaf-1: #c1440e; --leaf-2: #d97706; --leaf-3: #e0a106;
  --leaf-4: #8b3a1e; --leaf-5: #a33d1f; --leaf-6: #cc8400;
  --leaf-7: #c9b23e; --leaf-8: #b5b034; --leaf-9: #98a53a;
  --leaf-vein: rgba(40, 20, 5, .4);
  --deboss: #8E8E94;       /* the phone shelf you're not on: the board's #CDCDD2 was 1.5:1;
                              this is 3:1 (big text) and still clearly not Ink (Oct 26) */
  --dark: #0A0A0D;
  --dark-raised: #27272A;  /* hover on dark panels (tip cards) */
  --sky: #A8E9FF;          /* small text on dark: the logo gradient's first stop, lightened */
  --error: #B42318;
  --swipe-grey: #52525B;   /* Nov 10: swipe actions behind a row (white words on them) */
  --swipe-red: #B42318;
  --error-ink: #6B1D1D;
  --error-tint: #FDECEC;
  --ok-ink: #0B4F2A;
  --ok-tint: #E9F7EF;
  --brand-gradient: linear-gradient(90deg, #8EE2FE 0%, #50ACCB 26%, #308CB0 52%, #705E8F 72%, #C34683 100%);
  /* Cocamoo's AI (round 8, R8-AI-Style; COLOUR-RULES.md "The AI look"):
     plum means you're doing it; the gradient means Cocamoo's AI is.
     A deeper cut of the logo gradient: white text passes on every stop. */
  --ai: linear-gradient(120deg, #1E7299 0%, #4A5BA0 38%, #7B3F8E 68%, #A8336E 100%);
  --ai-ring: var(--ai);    /* gradient borders (brighter in dark mode) */
  --ai-shadow: inset 0 1px 0 rgba(255, 255, 255, .32), inset 0 -1px 0 rgba(0, 0, 0, .18), 0 6px 16px rgba(74, 91, 160, .3);
  --ai-soft: linear-gradient(120deg, #EEF6FB, #F2F1FA 50%, #FAEFF5);   /* the wash inside AI areas */
  --ai-ink: #4A5BA0;       /* small AI labels (gradient text only at 28px and up) */
  --ai-shimmer: linear-gradient(90deg, #D6E8F2, #E6E3F5, #F3DAE6);     /* placeholders */
  /* A gathering's meals (Nov 12, Dave chose "a little sky"; COLOUR-RULES.md,
     "Meal skies"): the sky at that hour, only on the small round plate
     behind a meal's drawing. The same in dark mode (it's a sky, and the
     drawing on it stays dark: --on-sky; night's is white). */
  --sky-breakfast: linear-gradient(160deg, #F7C6A3, #F6E0A4);  /* dawn */
  --sky-brunch: linear-gradient(160deg, #F6E0A4, #C9E6F3);     /* morning */
  --sky-lunch: linear-gradient(160deg, #B9E2F6, #DDF0F8);      /* noon */
  --sky-snacks: linear-gradient(160deg, #F3D58E, #F7E7BC);     /* afternoon */
  --sky-drinks: linear-gradient(160deg, #F2AE84, #F5CDA0);     /* golden hour */
  --sky-dinner: linear-gradient(160deg, #C9A5DB, #EDA6BD);     /* dusk */
  --sky-dessert: linear-gradient(160deg, #7F82CF, #4F5299);    /* night */
  --on-sky: #2A2A30;
  --sky-shadow: inset 0 1px 0 rgba(255, 255, 255, .5), inset 0 -2px 4px rgba(0, 0, 0, .08), 0 4px 10px rgba(24, 24, 27, .1);
  /* Food colours (Nov 14, Dave: "a colour set that's consistent and plays
     nicely with the site, then set to what's appropriate for the item":
     green beans a soft green, BBQ leaning brown). A soft circle and the
     food icon in a deeper shade of it, only behind a dish's icon in a
     gathering (COLOUR-RULES.md, "Food colours"; picked in food-icons.js). */
  --food-green: #DDEED6;  --food-green-ink: #3F7638;
  --food-red: #F6D8D4;    --food-red-ink: #A2392F;
  --food-orange: #FADFC8; --food-orange-ink: #AC5420;
  --food-yellow: #F6EAB9; --food-yellow-ink: #846511;
  --food-brown: #EADCCD;  --food-brown-ink: #74482A;
  --food-wheat: #F2E7D1;  --food-wheat-ink: #876538;
  --food-pink: #F7DCE5;   --food-pink-ink: #A23E65;
  --food-purple: #E5DCF0; --food-purple-ink: #66478E;
  --food-blue: #D8E8F3;   --food-blue-ink: #2D6A91;
  --food-cream: #EEECE5;  --food-cream-ink: #67624F;

  /* depth: light, not colour (COLOUR-RULES.md, "Depth") */
  --raise: 0 1px 2px rgba(24, 24, 27, .06), 0 8px 20px rgba(24, 24, 27, .06), inset 0 1px 0 rgba(255, 255, 255, .9);
  --well: inset 0 1px 3px rgba(24, 24, 27, .14), inset 0 0 0 .5px rgba(24, 24, 27, .06), inset 0 -1px 0 rgba(255, 255, 255, .55);
  --plum-button: linear-gradient(180deg, #B23C68, #9E2E57 55%, #8E2850);
  --plum-shadow: inset 0 1px 0 rgba(255, 255, 255, .3), inset 0 -1px 0 rgba(0, 0, 0, .2), 0 6px 14px rgba(158, 46, 87, .3);
  --plum-pressed-shadow: inset 0 2px 4px rgba(0, 0, 0, .25);
  /* Glass: frosted white that stays readable over anything (the grey
     ground, a course colour, a photo scrolling under the top bar). A white
     veil strong enough to calm what's behind, a blur that barely boosts its
     colour (the design's saturate(1.6) turned purples and oranges loud), a
     lit top edge and a hairline that keeps the edge crisp on light pages. */
  --glass: linear-gradient(180deg, rgba(255, 255, 255, .86), rgba(255, 255, 255, .74));
  --glass-blur: blur(30px) saturate(1.15) brightness(1.03);
  --glass-edge: rgba(255, 255, 255, .75);
  --glass-line: rgba(24, 24, 27, .08);
  --glass-shadow: inset 0 1px 0 rgba(255, 255, 255, .85), 0 0 0 .5px rgba(24, 24, 27, .08), 0 10px 30px rgba(24, 24, 27, .14);
  --photo: inset 0 0 0 1px rgba(0, 0, 0, .05), inset 0 -30px 40px rgba(0, 0, 0, .14);
  --deboss-shadow: 0 1px 0 #FFFFFF, 0 -1px 0 rgba(24, 24, 27, .12);

  /* type: Domine for titles, recipe names and step headings; Cabin for
     everything else. --font-mono and --font-number are older names some
     page styles still use; they now point at Cabin and Domine. */
  --font-body: Cabin, system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-display: Domine, Georgia, "Times New Roman", serif;
  --font-mono: var(--font-body);
  --font-number: var(--font-display);

  /* layout: the phone shell under 700px, the desktop shell from 700px */
  --page-max: 1320px;
  --gutter: clamp(16px, 4vw, 32px);
  --header-h: 0px;         /* the top bar's height (none on phones) */
  --tabbar-h: 96px;        /* room the floating tab bar takes at the bottom (phones) */
  /* Corners on the golden ratio (Dave, Oct 29; the proposal's "Corners on
     the golden ratio"): each step 1.618 times the last. These are the phone
     values, half a step smaller (desktop ÷ √1.618); desktop's are below.
     Nested corners: inner = outer − the gap between them (r-5 card with an
     r-3 gap → r-4 photo). Controls are pills (--r-pill); icon buttons,
     avatars and step numbers are circles (50%). */
  --r-1: 4px;      /* tags, the focus ring */
  --r-2: 6px;      /* checkboxes, tiny thumbnails */
  --r-3: 8px;      /* inputs that aren't pills, tiles inside lists */
  --r-4: 12px;     /* thumbnails, photos inside cards, icon tiles */
  --r-5: 20px;     /* cards, AI areas, the ribbon, dialogs */
  --r-6: 33px;     /* the top corners of bottom sheets */
  --r-pill: 999px; /* every button, chip, search box, segmented track */
  --radius: var(--r-3);
  --radius-card: var(--r-5);
  /* Button heights (Oct 29: slimmer, "too fat" on every breakpoint): small,
     medium (the default) and large (one moment per page at most). Phones
     here; desktop below. On touch screens the tap area stays 44px. */
  --btn-sm: 32px;
  --btn-md: 40px;
  --btn-lg: 48px;
  --shadow-menu: 0 16px 40px rgba(0, 0, 0, .14), 0 2px 8px rgba(0, 0, 0, .05);
  --ease: cubic-bezier(.2, .7, .2, 1);
}
@media (min-width: 700px) {
  :root { --header-h: 80px; --tabbar-h: 0px; } /* Nov 13: the floating island (10 + 60 + 10) */
  /* desktop: the full golden steps, and a pointer, so buttons can be slimmer */
  :root { --r-3: 10px; --r-4: 16px; --r-5: 26px; --r-6: 42px; --btn-sm: 30px; --btn-md: 36px; --btn-lg: 44px; }
}

/* Dark mode: the same tokens, darker values (handoff, "Dark mode"). Written
   now so nothing hard-codes a colour, but nothing sets data-theme="dark"
   yet: it's switched on at the end of the build, with Automatic / Light /
   Dark in Account. The ribbon and notes keep their light colours and Ink
   text, so nothing here reaches inside them.
   (Switched on Oct 26, step 7: layout.php's first script sets data-theme
   on <html> from Account › Appearance, cm-theme, or the device.) */
[data-theme="dark"] {
  /* food colours (Nov 14): a dim circle, the icon light */
  --food-green: #24331F;  --food-green-ink: #A3D59B;
  --food-red: #3B2321;    --food-red-ink: #F0A69D;
  --food-orange: #3B291B; --food-orange-ink: #F4B787;
  --food-yellow: #37301A; --food-yellow-ink: #E8CF77;
  --food-brown: #32261D;  --food-brown-ink: #D8B190;
  --food-wheat: #332C21;  --food-wheat-ink: #E0C89C;
  --food-pink: #39232E;   --food-pink-ink: #EFA6C1;
  --food-purple: #2C253D; --food-purple-ink: #C8B1EA;
  --food-blue: #1D2D39;   --food-blue-ink: #95C7EA;
  --food-cream: #2D2C28;  --food-cream-ink: #D5D0C1;
  --ai: linear-gradient(120deg, #2A86AE 0%, #5A6BB3 38%, #8C4E9F 68%, #B8417C 100%);
  --ai-ring: linear-gradient(120deg, #3FA4CF, #6F7FD0 40%, #A869BF 70%, #D3609A);
  --ai-soft: linear-gradient(120deg, #18212A, #1E1E2B 50%, #26192A);
  --ai-ink: #B9C3F2;
  --ai-shimmer: linear-gradient(90deg, #22343F, #2C2A40, #3A2533);
  --ground: #0E0E11;
  --card: #1C1C21;
  --well-bg: rgba(0, 0, 0, .45);
  --sep: #26262C;
  --line: #2E2E35;
  --ring: #3F3F46;
  --ink: #F4F4F5;
  --muted: #A1A1AA;
  --soft: #8A8A93;
  --faint: #5A5A63;
  --band: #26262C;
  --plum-text: #FFFFFF;
  --plum-icon: #C25A86;
  --deboss: #63636B;       /* 3.2:1 on the dark ground (Oct 26) */
  --paper: #EDE6D9;
  --paper-2: #E3D8C6;
  --note: #EDE6CC;
  --note-2: #E6DAB3;
  --raise: 0 1px 2px rgba(0, 0, 0, .45), 0 8px 20px rgba(0, 0, 0, .35), inset 0 1px 0 rgba(255, 255, 255, .07);
  --well: inset 0 1px 4px rgba(0, 0, 0, .6), inset 0 -1px 0 rgba(255, 255, 255, .05);
  --glass: linear-gradient(180deg, rgba(34, 34, 40, .84), rgba(24, 24, 29, .74));
  --glass-edge: rgba(255, 255, 255, .08);
  --glass-line: rgba(255, 255, 255, .08);
  --glass-shadow: inset 0 1px 0 rgba(255, 255, 255, .06), 0 0 0 .5px rgba(255, 255, 255, .06), 0 10px 30px rgba(0, 0, 0, .5);
  --deboss-shadow: 0 -1px 0 rgba(0, 0, 0, .7), 0 1px 0 rgba(255, 255, 255, .06);
  --shadow-menu: 0 16px 40px rgba(0, 0, 0, .55), 0 2px 8px rgba(0, 0, 0, .35);
  --error: #F97066;
  --swipe-grey: #3F3F46;
  --swipe-red: #C4321F;
  --error-button: #D92D20;
  --scrim: rgba(0, 0, 0, .62);
  --error-tint: rgba(249, 112, 102, .14);
  --error-ink: #FECDCA;
  --ok-tint: rgba(50, 213, 131, .14);
  --ok-ink: #A6F4C5;
  color-scheme: dark;
}
/* the recipe page sits a shade lighter in dark (handoff) */
[data-theme="dark"] body.page-recipe { --ground: #141418; }
/* Things that stay light in dark mode (the paper ribbon's bar and the yellow
   notes): their own tokens go back to light values, so their text stays Ink. */
[data-theme="dark"] .cm-row, [data-theme="dark"] .cm-stamp, [data-theme="dark"] #easy-read-recipe p.note, [data-theme="dark"] .import-note, [data-theme="dark"] .add-look-card, [data-theme="dark"] .add-tick.is-look {
  --ink: #18181B; --muted: #52525B; --soft: #6B6B73; --line: #E4E4E7; --ring: #D4D4D8; --card: #FFFFFF;
  --well-bg: rgba(24, 24, 27, .065); --plum-text: #9E2E57; color: #18181B;
}
/* Less transparency asked for, or a browser without backdrop blur: glass
   becomes a solid card. */
@media (prefers-reduced-transparency: reduce) {
  :root, [data-theme="dark"] { --glass: var(--card); --glass-blur: none; }
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  :root, [data-theme="dark"] { --glass: var(--card); }
}

/* ---- reset ---- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-padding-top: calc(var(--header-h) + 12px); }
body {
  margin: 0;
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.5;
  color: var(--ink);
  background: var(--ground);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* nothing may push the page sideways (a stray wide element would shift
     everything and clip the left edge, as Safari did on the home page) */
  overflow-x: clip;
  /* the footer sits at the bottom of the window on short pages */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
body > main { flex: 1 0 auto; }
/* Desktop backdrop: a soft glow of the logo's two ends at the top of the
   page, blurred and faint. The phone stays plain. */
@media (min-width: 700px) {
  body {
    background:
      radial-gradient(60% 680px at 8% -180px, rgba(110, 214, 252, .46), transparent 70%) no-repeat,
      radial-gradient(55% 640px at 94% -160px, rgba(195, 70, 131, .24), transparent 70%) no-repeat,
      var(--ground);
    /* Nov 14 (Dave): the glow holds still while the page scrolls over it,
       like the recipe page's doodles (desktop only: iOS ignores fixed) */
    background-attachment: fixed;
  }
}
img, svg, video { display: block; max-width: 100%; }
img { height: auto; }
a { color: var(--plum-text); text-decoration: none; }
a:hover { color: var(--plum-dark); }
[data-theme="dark"] a:hover { color: var(--plum-text); opacity: .82; } /* plum-dark would vanish on dark */
button { font: inherit; color: inherit; }
h1, h2, h3, h4, h5, h6 { margin: 0; line-height: 1.15; }
p { margin: 0; }
:focus-visible { outline: 2px solid var(--plum-icon); outline-offset: 2px; border-radius: var(--r-1); }
/* the browser's own surfaces, in the palette */
::selection { background: rgba(158, 46, 87, .2); color: var(--ink); }
input, textarea, select { caret-color: var(--plum-icon); accent-color: var(--plum-icon); }
/* While a sheet, dialog or the video overlay is open the page behind stays
   put. iOS Safari ignores this, so site.js also pins the body (Dave, Oct 26). */
html.sheet-open, html.overlay-open { overflow: hidden; }
html { scrollbar-color: var(--ring) transparent; }
.skip-link {
  position: absolute;
  left: 12px;
  top: -60px;
  z-index: 100;
  padding: 10px 16px;
  border-radius: var(--r-3);
  background: var(--plum);
  color: var(--white);
  font-weight: 700;
}
.skip-link:focus, .skip-link:hover { top: 10px; color: var(--white); }
.sr-only {
  position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
svg use { pointer-events: none; }

/* ---- signed in or not ----
   The server renders <body> with is-visitor, is-free (a free account) or
   is-member (paying), plus is-signed-in for both kinds of account
   (app/lib/auth.php, layout.php). Parts marked data-show only appear for:
     visitor     signed out
     signed-in   any account, free or member
     free        a free account
     member      a paying member
     not-member  signed out or free (where membership is offered) */
body:not(.is-visitor) [data-show="visitor"],
body:not(.is-signed-in) [data-show="signed-in"],
body:not(.is-free) [data-show="free"],
body:not(.is-member) [data-show="member"],
body.is-member [data-show="not-member"] { display: none !important; }
/* Just back from signing in on a page that redirects by sign-in state:
   hidden until site.js has told the server and reloaded. */
html.auth-gate body { visibility: hidden; }

/* ---- layout helpers ---- */
.wrap { width: 100%; max-width: var(--page-max); margin: 0 auto; padding: 0 var(--gutter); }
.wrap-narrow { width: 100%; max-width: 760px; margin: 0 auto; padding: 0 var(--gutter); }
.eyebrow {
  font: 400 12px/1.3 var(--font-mono);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--muted);
}
.mono { font-family: var(--font-mono); }

/* ---- the food doodle pattern ----
   A layer behind the content on a category colour: the recipe page and the
   category heading on /category/<slug>. On a light category colour
   (Breakfast yellow, .on-light) the white doodles turn dark instead. */
/* Nov 13: the new tile from the solid set (public/assets/img/doodles.svg,
   tools/doodle-tile.js) in white, in place of Webflow's PNG. Recipe pages
   no longer take it: their doodles are recipe.css's, in the course colour
   (the PNG showed through as white over them in dark mode). */
.doodle::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: #fff;
  opacity: .35;
  -webkit-mask: url("/assets/img/doodles.svg?v=1") 0 0 / 440px repeat;
          mask: url("/assets/img/doodles.svg?v=1") 0 0 / 440px repeat;
  pointer-events: none;
}
.on-light::before { background: #000; opacity: .18; }

/* ---- buttons ----
   Pills. Plum (the plum-button gradient) for the one main action; Raised
   (white, lifted) for everything else. Pressed sinks in.
   Oct 29 (the one button family): three heights from --btn-sm/md/lg,
   semibold except large; one filled button in sight at a time. Cocamoo's
   AI buttons (.ai-btn) are this same pill with the AI gradient. */
:where(.btn, .ai-btn) { position: relative; }
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: var(--btn-md);
  padding: 0 18px;
  border: 0;
  border-radius: var(--r-pill);
  font: 600 15px/1 var(--font-body);
  text-decoration: none;
  cursor: pointer;
  transition: box-shadow .15s, background .15s, color .15s, transform .1s;
  white-space: nowrap;
}
.btn:active { transform: translateY(1px); }
.btn svg { width: 16px; height: 16px; }
@media (min-width: 700px) { .btn { padding: 0 16px; font-size: 14px; } }
.btn-plum { background: var(--plum-button); box-shadow: var(--plum-shadow); color: var(--white); }
.btn-plum:hover { color: var(--white); filter: brightness(1.06); }
.btn-plum:active { background: var(--plum-pressed); box-shadow: var(--plum-pressed-shadow); filter: none; }
/* Raised: the one secondary button. A white card with a fine ring. Hover
   lifts it a little; pressing sets it down (it sinks to the surface and
   its shadow tightens) without going recessed: recessed means "selected"
   (wells, the lit row in On this page), not "being pressed". */
.btn-raised, .btn-line {
  background: var(--card);
  box-shadow: var(--raise), 0 0 0 1px var(--line);
  color: var(--ink);
}
.btn-raised:hover, .btn-line:hover {
  color: var(--plum-text);
  box-shadow: 0 1px 2px rgba(24, 24, 27, .06), 0 12px 26px rgba(24, 24, 27, .1), inset 0 1px 0 rgba(255, 255, 255, .9), 0 0 0 1px var(--ring);
}
.btn-raised:active, .btn-line:active {
  transform: translateY(1px) scale(.99);
  box-shadow: 0 1px 1px rgba(24, 24, 27, .12), 0 0 0 1px var(--ring);
  color: var(--plum-text);
}
.btn-lg { min-height: var(--btn-lg); padding: 0 24px; font-size: 16px; font-weight: 700; }
.btn-lg svg { width: 18px; height: 18px; }
@media (min-width: 700px) { .btn-lg { padding: 0 22px; font-size: 16px; } }
.btn-sm { min-height: var(--btn-sm); padding: 0 13px; font-size: 14px; }
@media (min-width: 700px) { .btn-sm { padding: 0 12px; font-size: 13px; } }
.btn-block { display: flex; width: 100%; }
/* touch: the tap area reaches 44px even when the button you see is slimmer */
@media (pointer: coarse) { .btn::after, .ai-btn::after { content: ""; position: absolute; inset: min(0px, calc((100% - 44px) / 2)) 0; } }

/* ---- surfaces ----
   .raised: a white card lifted off the ground (rounded 20px).
   .well:   pressed in, for things you type in or pick from. */
.raised { background: var(--card); border-radius: var(--radius-card); box-shadow: var(--raise); }
.well { background: var(--well-bg); box-shadow: var(--well); }

/* plain text link with an arrow ("See all 12") */
.text-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  gap: 4px;
  font-weight: 600;
  font-size: 15px;
  color: var(--plum-text);
  white-space: nowrap;
}
button.text-link { padding: 0; border: 0; background: none; font-family: inherit; cursor: pointer; } /* the same look as a button (it showed browser chrome) */
.text-link svg { width: 16px; height: 16px; transition: transform .15s; }
.text-link:hover svg { transform: translateX(2px); }

/* ==========================================================================
   DESKTOP SHELL: THE TOP BAR (700px and up)
   Glass. The logo; Library / Explore / Shopping list in a well (members) or
   Recipes / How it works / Pricing (visitors); then search, "+ Add a
   recipe" and the avatar (members) or Sign in / Join free (visitors).
   Search is a well from 1140px and an icon below that. On phones signed-in
   people have no top bar (the tab bar and each page's own title do that
   job); visitors keep a slim one with the logo, Sign in and Join free, so
   there's always a way in.
   ========================================================================== */
.topbar { display: none; }
@media (max-width: 699px) {
  body.is-visitor .topbar { display: block; position: relative; z-index: 50; background: var(--glass); -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur); border-bottom: 1px solid var(--glass-line); }
  body.is-visitor .topbar-inner { height: 60px; gap: 10px; padding: env(safe-area-inset-top) 16px 0; }
  body.is-visitor .topbar-logo { width: 104px; }
  body.is-visitor .topbar-right { gap: 8px; }
  body.is-visitor .topbar-right .btn { min-height: 44px; padding: 0 14px; font-size: 14px; }
}
/* Nov 13 (Dave, from the Community canvas's desktop board): the bar is a
   floating island, one glass pill a little in from the top and sides, the
   logo at its left end (its round letters sit tight in the pill's curve).
   The tabs are plain words in it (no well of their own: that well cost the
   room that pushed the logo out); the current one is grey with plum words,
   like every menu's chosen row. The strip around the island is clear. */
@media (min-width: 700px) {
  .topbar {
    display: block;
    position: sticky;
    top: 0;
    z-index: 50;
    padding: 10px var(--gutter);
    pointer-events: none; /* the page shows (and clicks) around the island */
  }
  .topbar .topbar-inner {
    pointer-events: auto;
    height: 60px;
    max-width: calc(var(--page-max) - 2 * var(--gutter));
    padding: 0 8px 0 22px;
    border-radius: var(--r-pill);
    background: var(--glass);
    -webkit-backdrop-filter: var(--glass-blur);
    backdrop-filter: var(--glass-blur);
    border: 1px solid var(--glass-edge);
    box-shadow: var(--glass-shadow);
    gap: 14px;
  }
  .topbar .seg { padding: 0; gap: 0; background: none; box-shadow: none; }
  .topbar .seg a { height: 40px; padding: 0 14px; }
  .topbar .seg a.is-current { background: var(--well-bg); box-shadow: none; color: var(--plum-text); }
  .topbar .seg a.has-count { padding-right: 44px; }
  .topbar .topbar-logo { width: 116px; }
  .topbar .topbar-right { gap: 8px; }
  .topbar .round-btn, .topbar .topbar-right .btn { box-shadow: none; }
  .topbar .round-btn:not(.avatar-btn) { background: transparent; }
  .topbar .round-btn:not(.avatar-btn):hover, .topbar .round-btn[aria-expanded="true"] { background: var(--well-bg); }
  .topbar .topbar-add { background: var(--well-bg); }
  .topbar .topbar-more { margin-left: -8px; }
}
@media (prefers-reduced-transparency: reduce) { .topbar .topbar-inner { background: var(--card); -webkit-backdrop-filter: none; backdrop-filter: none; } }
.topbar-inner {
  display: flex;
  align-items: center;
  gap: 24px;
  height: var(--header-h);
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 0 var(--gutter);
}
.topbar-logo { display: block; width: 128px; flex: 0 0 auto; }
.topbar-logo .logo-svg { width: 100%; height: auto; }
/* round 10: the signed-out home's own links (header.php), and Sign in as a
   plain link there so Try it free is the bar's one button */
.topbar-links { display: flex; align-items: center; gap: 4px; margin-left: 20px; }
.topbar-links a, .topbar-signin { display: inline-flex; align-items: center; min-height: 40px; padding: 0 12px; border-radius: var(--r-pill); color: var(--ink); font-weight: 600; }
.topbar-links a:hover, .topbar-signin:hover { background: var(--well-bg); color: var(--ink); }
@media (max-width: 899px) { .topbar-links { display: none; } }

/* Library / Explore / Shopping list: a segmented control in a well */
.seg { display: flex; gap: 2px; padding: 4px; border-radius: var(--r-pill); background: var(--well-bg); box-shadow: var(--well); }
.seg a, .seg button {
  display: flex;
  align-items: center;
  gap: 6px;
  height: 36px;
  padding: 0 18px;
  border-radius: var(--r-pill);
  color: var(--muted);
  font-size: 15px;
  font-weight: 600;
  white-space: nowrap;
}
.seg button { border: 0; background: none; font-family: inherit; cursor: pointer; }
.seg a:hover, .seg button:hover { color: var(--ink); }
.seg a.is-current, .seg button.is-current { background: var(--card); box-shadow: var(--raise); color: var(--ink); font-weight: 700; }
.seg-count {
  padding: 1px 7px;
  border-radius: var(--r-2);
  background: var(--card);
  box-shadow: var(--raise);
  font-size: 12px;
  font-weight: 700;
  color: var(--plum-text);
}
.seg-count:empty { display: none; }
/* Nov 12, later (Dave): Gatherings as a tab; with gatherings to come, the
   number inside it is its own button (the list of them) while the word goes
   straight to the next one (header.php) */
.seg-split { position: relative; display: flex; }
.seg a.has-count { padding-right: 46px; }
.seg .seg-menu-btn {
  position: absolute; top: 50%; right: 10px; transform: translateY(-50%);
  display: inline-flex; align-items: center; justify-content: center;
  height: 22px; min-width: 24px; padding: 0 7px;
  border-radius: var(--r-2); background: var(--card); box-shadow: var(--raise);
  color: var(--plum-text); font: 700 12px var(--font-body);
}
.seg .seg-menu-btn::before { content: ""; position: absolute; inset: -11px -8px; } /* a bigger target than it looks */
.seg .seg-menu-btn:hover, .seg .seg-menu-btn[aria-expanded="true"] { background: var(--plum-button); box-shadow: var(--plum-shadow); color: var(--white); }
.seg .seg-menu-btn:focus-visible { outline: 2px solid var(--plum-icon); outline-offset: 2px; }


.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 12px; }
.topbar-right .btn { min-height: 42px; }
/* Round 11 (Nov 12): the search box went (six tabs); search is the round
   button (.topbar-search-icon) at every width */
.round-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  flex: 0 0 auto;
  border: 0;
  border-radius: 50%;
  background: var(--card);
  box-shadow: var(--raise);
  color: var(--ink);
  font: 700 15px var(--font-display);
  cursor: pointer;
}
.round-btn svg { width: 18px; height: 18px; }
.round-btn:hover, .round-btn[aria-expanded="true"] { color: var(--plum-text); }
.round-btn[aria-expanded="true"] { box-shadow: var(--well); background: var(--well-bg); }

/* Nov 12: ··· right of the tabs (header.php); a round icon button like
   search, desktop shell only (phones reach these from the Account tab) */
.topbar-more { margin-left: -12px; } /* sits with the tabs, not a full gap away */
@media (max-width: 699px) { .topbar-more { display: none; } }
/* The island's widths (Nov 13, Dave: the logo should leave only once, and
   as late as it can). Things give way in this order, and nothing comes
   back as the window narrows further:
     below 1200px  Add a recipe becomes "+"
     below 1080px  "Shopping list" becomes "Shopping", the tabs a little tighter
     below 960px   "List", as on phones; a smaller logo; the ··· folds into
                   the avatar's menu (About and Help join it there)
     below 840px   the logo steps aside (the Home tab does its job), the
                   tabs tighter again
   (Round 11, Nov 12, had the logo go at 1000px, back then the tabs sat in
   their own well inside a full-width bar.) */
.seg-short { display: none; }
@media (max-width: 1199px) {
  /* "+" only; the words stay for screen readers */
  .topbar-add-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; }
  .topbar-add { width: 42px; padding: 0; }
}
@media (max-width: 1079px) {
  .seg-long { display: none; }
  .topbar .seg a { padding: 0 11px; }
  .topbar .seg a.has-count { padding-right: 40px; }
  .topbar .topbar-inner { gap: 10px; }
}
.ms-row.ms-fold { display: none; }
@media (max-width: 959px) {
  .seg-shop { display: none; }
  .seg-short { display: inline; } /* "List", as on phones */
  .topbar .topbar-logo { width: 96px; }
  .topbar .topbar-inner { padding-left: 18px; }
  .topbar .seg a { padding: 0 10px; }
  .topbar .seg a.has-count { padding-right: 38px; }
  body.is-signed-in .topbar-more { display: none; }
  .ms-row.ms-fold { display: flex; }
}
@media (max-width: 839px) {
  body.is-signed-in .topbar-logo { display: none; } /* visitors have no tabs: their logo stays */
  .topbar { padding-left: 12px; padding-right: 12px; }
  .topbar .topbar-inner { gap: 6px; padding-left: 12px; } /* Nov 13: room before Home (was 6px) */
  .topbar .seg a { padding: 0 9px; }
  .topbar .seg a.has-count { padding-right: 36px; }
  .seg .seg-menu-btn { right: 7px; }
  .topbar .topbar-right { gap: 4px; }
  .topbar .topbar-more { margin-left: -4px; }
}

/* panels that drop from the top bar: the avatar's menu and search */
.hdr-panels { position: relative; max-width: var(--page-max); margin: 0 auto; }
.hdr-panel {
  position: absolute;
  top: 6px;
  z-index: 2;
  width: min(340px, calc(100vw - 24px));
  max-height: calc(100vh - var(--header-h) - 24px);
  overflow: auto;
  padding: 8px;
  background: var(--glass);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border-radius: var(--r-4);
  box-shadow: var(--glass-shadow), var(--shadow-menu);
  animation: panel-in .18s var(--ease);
}
.hdr-panel[hidden] { display: none; }
.panel-account { right: var(--gutter); width: min(300px, calc(100vw - 24px)); } /* Nov 13: the ☰ sheet's rows (.ms-row) */
.panel-more { left: var(--gutter); width: min(300px, calc(100vw - 24px)); } /* site.js moves it under the ··· button */
.ms-list { gap: 0; }
.ms-list .ms-row { min-height: 50px; padding: 0 10px; }
.ms-list .ms-you { min-height: 64px; }
.ms-list .ms-avatar { width: 44px; height: 44px; }
.panel-search { right: var(--gutter); width: min(560px, calc(100vw - 24px)); padding: 12px; }
@keyframes panel-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
.hdr-scrim {
  position: fixed;
  inset: var(--header-h) 0 0 0;
  z-index: 40;
  background: rgba(10, 10, 13, .18);
  /* The panels sit inside the glass top bar, and a blur inside a blur only
     sees its parent, so the panel's own blur never reached the page: the
     page showed through the search panel crisp (Dave, Oct 28). The scrim
     is outside the bar, so it softens the page behind instead. */
  -webkit-backdrop-filter: blur(10px) saturate(.9);
  backdrop-filter: blur(10px) saturate(.9);
  animation: fade-in .2s ease;
}
@media (prefers-reduced-transparency: reduce) { .hdr-scrim { -webkit-backdrop-filter: none; backdrop-filter: none; background: rgba(10, 10, 13, .45); } }
.hdr-scrim[hidden] { display: none; }
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

.menu-list { display: flex; flex-direction: column; gap: 2px; }
.menu-who { padding: 10px 12px 0; font-weight: 700; }
.menu-plan { padding: 0 12px 8px; font-size: 14px; color: var(--muted); }
.menu-link {
  display: block;
  padding: 10px 12px;
  border-radius: var(--r-3);
  color: var(--ink);
  font-size: 16px;
}
.menu-link:hover, .menu-link:focus-visible { background: var(--well-bg); color: var(--ink); outline: none; } /* see-through grey: right on glass over anything */
/* the page you're on: grey with plum words, not a solid plum fill, so it never
   looks like the page's one main button (Dave, Nov 10) */
.menu-link.is-current, .menu-link.is-current:hover { background: var(--well-bg); color: var(--plum-text); font-weight: 700; }

/* quick search */
.qs-form { position: relative; display: flex; align-items: center; }
.qs-icon { position: absolute; left: 14px; width: 20px; height: 20px; color: var(--muted); }
.qs-form input {
  width: 100%;
  height: 46px;
  padding: 0 48px 0 44px;
  border: 0;
  border-radius: var(--r-pill);
  font: 400 16px var(--font-body);
  color: var(--ink);
  background: var(--well-bg);
  box-shadow: var(--well);
  -webkit-appearance: none;
  appearance: none;
}
.qs-form input:focus { outline: none; box-shadow: var(--well), 0 0 0 2px var(--plum-icon); }
.qs-form input::-webkit-search-cancel-button { display: none; }
.qs-close { position: absolute; right: 3px; width: 40px; height: 40px; border: 0; border-radius: 50%; background: none; color: var(--muted); cursor: pointer; display: flex; align-items: center; justify-content: center; }
.qs-close svg { width: 20px; height: 20px; }
.qs-close:hover { background: var(--band); color: var(--ink); }
.qs-results { margin-top: 8px; }
.qs-group { padding: 6px 0; }
.qs-group + .qs-group { border-top: 1px solid var(--line); }
.qs-label { padding: 6px 10px 4px; font: 600 14px/1.3 var(--font-body); color: var(--muted); }
.qs-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 7px 10px;
  border-radius: var(--r-3);
  color: var(--ink);
}
.qs-item:hover, .qs-item.is-active { background: var(--well-bg); color: var(--ink); }
.qs-thumb { flex: 0 0 44px; width: 44px; height: 44px; border-radius: var(--r-2); object-fit: cover; background: var(--band); }
.qs-tile { flex: 0 0 44px; width: 44px; height: 44px; border-radius: var(--r-2); background: var(--cat, var(--muted)); display: flex; align-items: center; justify-content: center; color: var(--on-cat, var(--white)); }
.qs-tile svg { width: 20px; height: 20px; }
.qs-text { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.qs-name { font-weight: 600; font-size: 15px; line-height: 1.25; }
.qs-why { font-size: 13px; color: var(--muted); }
.qs-name mark, .hl mark {
  background: none;
  color: inherit;
  font-weight: 800;
  text-decoration: underline;
  text-decoration-color: var(--plum-icon);
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
}
.qs-chips { display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 10px 8px; }
.qs-chip { padding: 6px 12px; border: 1px solid var(--line); border-radius: var(--r-pill); color: var(--ink); font-size: 14px; background: var(--card); }
.qs-chip:hover { border-color: var(--plum-icon); color: var(--plum-icon); }
.qs-all { display: flex; justify-content: space-between; align-items: center; margin-top: 4px; padding: 12px 10px 4px; border-top: 1px solid var(--line); }
.qs-empty { padding: 18px 10px; color: var(--muted); }

.tk-tag {
  display: inline-block;
  margin-left: 6px;
  padding: 2px 7px 1px;
  border-radius: var(--r-1);
  background: var(--band);
  color: var(--muted);
  font: 500 12px/1.5 var(--font-mono);
  letter-spacing: .09em;
  text-transform: uppercase;
  vertical-align: .15em;
  white-space: nowrap;
}

/* ==========================================================================
   RECIPE CARDS (home, search, landing, recipe page "more recipes")
   ========================================================================== */
.card {
  display: flex;
  flex-direction: column;
  gap: 8px;
  color: var(--ink);
  min-width: 0;
}
.card:hover { color: var(--ink); }
.card-media {
  position: relative;
  aspect-ratio: 4 / 3;
  border-radius: var(--r-4);
  overflow: hidden;
  background: var(--band);
}
.card-media img { width: 100%; height: 100%; object-fit: cover; transition: transform .4s var(--ease); }
.card:hover .card-media img { transform: scale(1.03); }
/* Test Kitchen tile: category colour with a flask, same shape as a photo */
.card-tile {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: var(--cat, var(--muted));
  color: var(--on-cat, var(--white));
  background-image: radial-gradient(circle at 30% 25%, rgba(255,255,255,.14), transparent 55%);
}
.card-tile svg { width: 34%; max-width: 72px; height: auto; }
.card-tile span { font: 500 12px/1 var(--font-mono); letter-spacing: .14em; text-transform: uppercase; }
/* AI recipe tiles (Nov 10, Dave: "Aurora, bold sparkles";
   claude/ai-tiles/README.md, drawn by js/ai-tile.js): an AI-written recipe
   with no photo. Course colour, two AI glows tuned per course, white line
   ingredients and white sparkles top left. Its own colour field, the same
   in light and dark. It fills whatever thumbnail box it's put in, which
   sets the size and corners; inside, everything is measured against that
   box (cqw/cqh) so it reads the same at 44px and on a wide card. */
:has(> .ai-tile) { position: relative; overflow: hidden; }
:has(> .ai-tile) > img { position: relative; z-index: 1; } /* Nov 13: a photo sits over the tile; if it won't load (onerror removes it) the tile shows */
.ai-tile { position: absolute; inset: 0; display: block; container-type: size; border-radius: inherit; }
.ai-tile .ai-ic {
  position: absolute;
  display: block;
  fill: none;
  stroke: #FFFFFF;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.ai-tile .ai-spark {
  position: absolute;
  left: max(4px, 6cqh);
  top: max(4px, 6cqh);
  width: max(16px, 26cqh);
  height: max(16px, 26cqh);
  filter: drop-shadow(0 1px 3px rgba(20, 20, 40, .35));
}
.ai-tile .ai-spark path { fill: #FFFFFF; stroke: #FFFFFF; stroke-width: 1.6; stroke-linejoin: round; }
@container (max-height: 89.98px) { .ai-tile .ai-ic { stroke-width: 2.3; } } /* a touch heavier when small */
.card-badge {
  position: absolute;
  top: 8px;
  left: 8px;
  padding: 3px 8px;
  border-radius: var(--r-2);
  background: var(--plum);
  color: var(--white);
  font: 500 12px/1.4 var(--font-mono);
  letter-spacing: .1em;
  text-transform: uppercase;
}
/* "Free to cook" / "Free this week": a glass tag on the photo (or a pill on its own) */
.free-tag, .card-free {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  border-radius: var(--r-pill);
  background: var(--glass);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  box-shadow: var(--glass-shadow);
  color: var(--plum-text);
  font: 700 12px/1.2 var(--font-body);
}
.card-free { position: absolute; top: 8px; left: 8px; }
/* members' recipes, for anyone who isn't a member: a glass padlock on the photo */
.card-lock {
  position: absolute; top: 8px; right: 8px; display: flex; align-items: center; justify-content: center; width: 32px; height: 32px;
  border-radius: 50%; background: var(--glass); -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur); box-shadow: var(--glass-shadow); color: var(--ink);
}
.card-lock svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.card-name { font-weight: 700; font-size: 15px; line-height: 1.25; }
.card-meta { font: 400 14px/1.3 var(--font-body); color: var(--muted); }
.card-why { font-size: 13px; color: var(--muted); margin-top: -4px; }
.card-fit { font-size: 13px; color: var(--muted); font-weight: 600; margin-top: -4px; } /* not plum: it isn't tappable */
.card-lg .card-name { font-family: var(--font-display); font-size: 22px; }

/* ==========================================================================
   PAGE LAYOUT: THE MAIN COLUMN AND THE RAILS
   <div class="shell [has-left] [has-right]">
     <aside class="shell-left">…</aside>    Left rail: from 1200px
     <div class="shell-main">…</div>
     <aside class="shell-right">…</aside>   Right rail: from 900px
   </div>
   When a rail doesn't fit it drops below the main column (right rail
   first). Rails are stacks of .raised cards.
   ========================================================================== */
.shell {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 24px;
  max-width: var(--page-max);
  margin: 0 auto;
  padding: calc(28px + env(safe-area-inset-top)) 16px 40px; /* phones: no top bar, so room at the top */
}
.shell-main { min-width: 0; }
.shell-left, .shell-right { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
@media (min-width: 700px) {
  .shell { gap: 28px; padding: 28px var(--gutter) 72px; }
}
@media (min-width: 900px) {
  .shell.has-right { grid-template-columns: minmax(0, 1fr) 300px; align-items: start; }
  .shell.has-right .shell-main { grid-column: 1; grid-row: 1; }
  .shell.has-right .shell-right { grid-column: 2; grid-row: 1 / span 2; }
  .shell.has-right .shell-left { grid-column: 1; grid-row: 2; }
}
@media (min-width: 1200px) {
  .shell.has-left { grid-template-columns: 230px minmax(0, 1fr); align-items: start; }
  .shell.has-left .shell-left { grid-column: 1; grid-row: 1; position: sticky; top: calc(var(--header-h) + 20px); }
  .shell.has-left .shell-main { grid-column: 2; grid-row: 1; }
  .shell.has-left.has-right { grid-template-columns: 230px minmax(0, 1fr) 300px; }
  .shell.has-left.has-right .shell-right { grid-column: 3; grid-row: 1; }
}

/* ==========================================================================
   THE PHONE TITLE ROW (round 11, Nov 12; partials/tab-head.php)
   Replaced the Explore / Library shelves: a plain Domine title (the logo on
   Home), then round buttons, search and ☰ (44px, their tap size). From
   700px the top bar does this job.
   ========================================================================== */
.tab-head { display: flex; align-items: center; gap: 10px; min-height: 48px; margin: 0 4px; }
.tab-head-title { margin: 0; font: 700 32px/1.1 var(--font-display); letter-spacing: -.02em; color: var(--ink); }
.tab-head-logo { margin: 0; display: flex; align-items: center; }
.tab-head-logo .logo-svg { width: 132px; height: auto; display: block; }
.tab-head-btns { margin-left: auto; display: flex; align-items: center; gap: 8px; }
.tab-head .round-btn { width: 44px; height: 44px; background: var(--card); box-shadow: var(--raise), 0 0 0 1px var(--line); }
.tab-head .round-btn svg { width: 21px; height: 21px; }
@media (min-width: 700px) { .tab-head { display: none; } }
/* a page with both: the title row on phones, its own big title from 700px */
@media (max-width: 699px) { .tab-head ~ .page-title { display: none; } }

/* ==========================================================================
   THE ☰ SHEET (round 11, phones; partials/menu-sheet.php, site.js)
   A glass bottom sheet: you, Gatherings, Appearance, Help, Privacy · Terms,
   Sign out. Never shown from 700px (the avatar menu and ··· do it there).
   ========================================================================== */
.menu-sheet-scrim { position: fixed; inset: 0; z-index: 70; background: rgba(10, 10, 13, .32); animation: fade-in .2s ease; }
.menu-sheet-scrim[hidden], .menu-sheet[hidden] { display: none; }
.menu-sheet {
  position: fixed; left: 8px; right: 8px; bottom: calc(8px + env(safe-area-inset-bottom)); z-index: 71;
  display: flex; flex-direction: column;
  max-height: calc(100vh - 40px); overflow: auto;
  padding: 10px 8px 12px;
  border-radius: var(--r-6);
  background: var(--glass);
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-edge);
  box-shadow: var(--glass-shadow), var(--shadow-menu);
  animation: ms-in-up .24s var(--ease);
}
@keyframes ms-in-up { from { transform: translateY(40px); opacity: 0; } to { transform: none; opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .menu-sheet, .menu-sheet-scrim { animation: none; } body.tabbar-auto .tabbar { transition: none; } }
@media (min-width: 700px) { .menu-sheet, .menu-sheet-scrim { display: none !important; } }
.menu-sheet-grip { align-self: center; width: 40px; height: 5px; margin-bottom: 8px; border-radius: 3px; background: var(--ring); }
.ms-row { display: flex; align-items: center; gap: 14px; min-height: 52px; padding: 0 14px; border-radius: var(--r-4); color: var(--ink); font-size: 16px; }
a.ms-row:hover, a.ms-row:focus-visible { background: var(--well-bg); }
.ms-you { min-height: 68px; }
.ms-avatar { position: relative; flex: 0 0 auto; width: 48px; height: 48px; border-radius: 50%; overflow: hidden; display: flex; align-items: center; justify-content: center; background: var(--plum-button); color: var(--white); font: 700 20px var(--font-display); }
.ms-avatar img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.ms-text { flex: 1; min-width: 0; display: flex; flex-direction: column; line-height: 1.25; }
.ms-text b { font-size: 17px; font-weight: 700; }
.ms-text small { font-size: 13px; color: var(--muted); }
.ms-chev { width: 18px; height: 18px; color: var(--muted); flex: 0 0 auto; }
.ms-ic { flex: 0 0 auto; width: 36px; height: 36px; border-radius: 50%; background: var(--well-bg); color: var(--ink); display: flex; align-items: center; justify-content: center; }
.ms-ic svg { width: 19px; height: 19px; }
.ms-count { min-width: 24px; height: 24px; padding: 0 7px; border-radius: var(--r-pill); background: var(--card); box-shadow: var(--raise); color: var(--plum-text); font: 700 13px/24px var(--font-body); text-align: center; }
.ms-sub { min-height: 44px; padding-left: 64px; color: var(--plum-text); font-weight: 600; font-size: 15px; }
.ms-sep { height: 1px; margin: 4px 14px; background: var(--line); }
.ms-small { min-height: 44px; gap: 6px; color: var(--muted); font-size: 15px; }
.ms-small a { color: var(--muted); padding: 10px 0; }
.ms-small a:hover { color: var(--ink); }
.ms-signout { min-height: 48px; font-weight: 600; }

.row-thumb { flex: 0 0 auto; width: 64px; height: 64px; border-radius: var(--r-4); overflow: hidden; background: var(--band); position: relative; }
.row-thumb img { width: 100%; height: 100%; object-fit: cover; }
.row-thumb::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .05), inset 0 -18px 24px rgba(0, 0, 0, .12); }
.row-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.row-name { font-size: 17px; font-weight: 600; line-height: 1.25; }
.row-meta { font-size: 14px; color: var(--muted); }
.row-meta b { font-size: 12px; letter-spacing: .08em; text-transform: uppercase; color: var(--plum-text); }
.dot { width: 8px; height: 8px; flex: 0 0 auto; border-radius: 50%; background: var(--cat, var(--muted)); }
.side-box { display: flex; flex-direction: column; gap: 8px; padding: 16px 16px 14px; }
.side-box[hidden] { display: none; }
.side-title { margin-bottom: 2px; font: 700 18px var(--font-display); }
.side-note { font-size: 13px; line-height: 1.45; color: var(--muted); }
.side-note[hidden] { display: none; }
.side-link { align-self: flex-start; padding: 2px 0; border: 0; background: none; color: var(--plum-text); font: 700 14px var(--font-body); cursor: pointer; }
.diet-check { display: flex; align-items: center; gap: 10px; min-height: 32px; font-size: 15px; cursor: pointer; }
.diet-check input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.check-box { position: relative; flex: 0 0 auto; width: 20px; height: 20px; border-radius: var(--r-2); background: var(--well-bg); box-shadow: var(--well), 0 0 0 1px var(--line); }
.diet-check input:checked + .check-box { background: var(--plum-button); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .3), 0 3px 8px rgba(158, 46, 87, .3); }
.diet-check input:checked + .check-box::after { content: ""; position: absolute; left: 7px; top: 3px; width: 5px; height: 10px; border: solid var(--white); border-width: 0 2.4px 2.4px 0; transform: rotate(45deg); }
.diet-check input:focus-visible + .check-box { outline: 2px solid var(--plum-icon); outline-offset: 2px; }

/* ==========================================================================
   PLAN CARDS: the shared pieces of partials/plan-choices.php (the home
   page, /join, signed-out Explore) and /upgrade. Since Nov 12 both cards
   are light (the old dark "Two ways in", partials/plans.php, is gone).
   ========================================================================== */
.plan-card { display: flex; flex-direction: column; gap: 10px; padding: 26px 26px 24px; border-radius: var(--r-5); }
.plan-name { font: 700 26px var(--font-display); }
.plan-price { font: 700 40px/1 var(--font-body); }
.plan-price span { font-size: 18px; font-weight: 500; }
.plan-card .btn { margin-top: 8px; }

/* ==========================================================================
   JOIN AND BECOME A MEMBER (Nov 10, 2026; pages/join.php, pages/upgrade.php,
   partials/membership.php). The plan cards above, larger, each with the list
   of what it gets: a tick for yes, a dash for members only. On Join the
   member card leads (first on phones, on the left from 700px); its white
   button is the one filled button in view.
   ========================================================================== */
.join-page, .upgrade-page { max-width: 1040px; margin: 0 auto; padding: 28px 16px 64px; }
.upgrade-page { max-width: 720px; display: grid; gap: 20px; }
.join-head { max-width: 640px; margin: 0 auto 26px; text-align: center; }
.join-page-title { font: 700 34px/1.1 var(--font-display); letter-spacing: -.02em; text-wrap: balance; }
.join-page-lead { margin-top: 12px; font-size: 18px; line-height: 1.55; color: var(--muted); text-wrap: balance; }
.join-choices { display: grid; gap: 16px; align-items: start; }
.join-choice { gap: 12px; }
/* Nov 11 (impeccable pass): the plan's tag is a pill on the name's line,
   tinted from its own text colour so it suits both cards and both themes */
.join-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; width: 100%; }
.join-head .plan-name { text-align: left; }
.join-tag { padding: 4px 10px; border-radius: var(--r-pill); background: color-mix(in srgb, currentColor 14%, transparent); font: 600 13px/1.3 var(--font-body); color: var(--sky); white-space: nowrap; }
.join-tag-quiet { color: var(--muted); }
.join-terms { font-size: 15px; line-height: 1.5; color: var(--muted); }
.join-choice .btn { margin: 4px 0 8px; }
.join-foot { margin-top: 26px; text-align: center; color: var(--muted); }
.perk-list { display: grid; gap: 0; margin: 0; padding: 0; list-style: none; border-top: 1px solid var(--line); }
.perk { display: flex; gap: 12px; align-items: flex-start; padding: 11px 0; border-bottom: 1px solid var(--line); font-size: 16px; line-height: 1.45; color: var(--ink); }
.perk-mark { display: grid; flex: 0 0 auto; place-items: center; width: 22px; height: 22px; margin-top: 1px; border-radius: 50%; background: var(--plum-tint); color: var(--plum-icon); }
.perk-mark svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round; }
.perk-no { color: var(--muted); }
.perk-no .perk-mark { background: none; color: var(--muted); }
/* round 10: "New" beside a row (the Gatherings rows) */
.perk-new { display: inline-flex; align-items: center; height: 22px; margin-left: 6px; padding: 0 9px; border-radius: var(--r-pill); background: var(--plum-tint); color: var(--plum-text); font: 700 12px/1 var(--font-body); vertical-align: 1px; }
.perk-no .perk-new { background: var(--well-bg); color: var(--muted); }
/* Nov 12 (Dave): the groups, "Both plans" then "Members also get" */
.perk-group { padding: 18px 0 8px; border-bottom: 1px solid var(--line); font: 700 13px/1.3 var(--font-body); letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.perk-list > .perk-group:first-child { padding-top: 6px; }
.perk-list:has(> .perk-group) { border-top: 0; }
/* Nov 12 (Dave): no line under the last row of each group */
.perk:last-child, .perk:has(+ .perk-group) { border-bottom: 0; }
/* Nov 12 (Dave): /join's and the home page's cards, both light. The
   members' card white with a plum outline; the free card quiet. */
.join-member { background: var(--card); box-shadow: var(--raise), inset 0 0 0 1.5px var(--plum-icon); }
.join-member .join-tag { color: var(--plum-text); }
.join-free { background: var(--card); box-shadow: var(--raise); }
@media (min-width: 700px) {
  .join-page, .upgrade-page { padding-top: 44px; }
  .join-page-title { font-size: 46px; }
  .join-choices { grid-template-columns: 1fr 1fr; gap: 22px; }
  /* both cards share the rows, so each tick lines up with its twin across the page */
  .join-choice { display: grid; grid-row: span 6; grid-template-rows: subgrid; align-content: start; padding: 32px; }
}
/* Become a member: the light, plum-outlined card sells it (dark until Nov 12), the raised card below says what stays */
.upgrade-hero { gap: 14px; padding: 28px 22px; }
.upgrade-title { font: 700 30px/1.12 var(--font-display); letter-spacing: -.02em; color: var(--ink); text-wrap: balance; } /* Nov 12: on the light card now */
.upgrade-hero > .join-tag { align-self: flex-start; }
.upgrade-lead { color: var(--muted); }
.upgrade-save { margin-top: -6px; font-size: 15px; color: var(--muted); }
.upgrade-fine { color: var(--muted); }
.upgrade-lead { font-size: 17px; line-height: 1.55; }
.upgrade-buy { display: grid; gap: 12px; margin-top: 8px; }
.upgrade-buy .plan-price { line-height: 1.2; } /* its small words wrap on phones */
.upgrade-fine { font-size: 14px; line-height: 1.5; }
.upgrade-keep { display: grid; gap: 14px; padding: 24px 22px; }
.upgrade-keep-h { font: 700 20px/1.25 var(--font-display); }
@media (min-width: 700px) {
  .upgrade-hero { padding: 40px; }
  .upgrade-title { font-size: 40px; }
  .upgrade-keep { padding: 28px 40px; }
}

/* ==========================================================================
   TOAST: the dark pill at the bottom, with Undo (Cocamoo.toast, site.js).
   Above the tab bar on phones.
   ========================================================================== */
.cm-toast {
  position: fixed;
  left: 50%;
  bottom: calc(28px + var(--tabbar-h) + env(safe-area-inset-bottom));
  display: flex;
  align-items: center;
  gap: 14px;
  z-index: 1100;
  width: max-content;                 /* round 9: longer messages ("Swapped. Beef and */
  max-width: calc(100vw - 32px);      /* Broccoli will show it too.") use the width, */
  padding: 10px 16px;
  border-radius: var(--r-5);                /* and stay a pill on one line */
  background: var(--ink);
  color: var(--white);
  font-size: 14px;
  font-weight: 600;
  transform: translate(-50%, 20px);
  opacity: 0;
  pointer-events: none;
  transition: transform .2s ease, opacity .2s ease;
}
.cm-toast.is-on { transform: translate(-50%, 0); opacity: 1; pointer-events: auto; }
.cm-toast button { border: 0; background: none; padding: 4px 2px; color: var(--sky); font: 700 14px var(--font-body); cursor: pointer; }
.cm-toast button { min-height: 44px; min-width: 44px; padding: 0 8px; margin: -8px -8px -8px 0; } /* a full tap target inside the slim pill */
.cm-toast .cm-toast-extra { margin-left: 4px; color: var(--white); text-decoration: underline; text-underline-offset: 3px; } /* Nov 10: "Add 12 items to your list" */
body.no-tabbar .cm-toast { bottom: calc(28px + env(safe-area-inset-bottom)); }

/* page titles (Domine) and the simple pages built from rows of a raised card:
   Account for now, and the stand-ins for pages still to be built (soon.php) */
.page-title { margin: 0 4px 18px; font: 700 32px/1.1 var(--font-display); letter-spacing: -.02em; }
@media (min-width: 700px) { .page-title { font-size: 40px; } }
.soon-card { display: flex; flex-direction: column; align-items: flex-start; gap: 12px; max-width: 620px; padding: 24px; }
.soon-title { font: 700 20px/1.25 var(--font-display); }
.soon-lead { font-size: 17px; line-height: 1.5; }
.soon-note { font-size: 14px; color: var(--muted); }
.soon-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 4px; }
.row-list { display: flex; flex-direction: column; max-width: 620px; margin-top: 16px; overflow: hidden; }
.row-list a { display: flex; flex-direction: column; gap: 2px; min-height: 56px; justify-content: center; padding: 12px 20px; color: var(--ink); font-size: 17px; }
.row-list a + a { border-top: 1px solid var(--sep); }
.row-list a:hover { background: var(--band); color: var(--ink); }
.row-note { font-size: 14px; color: var(--muted); }
.account-now .soon-card + .row-list { margin-top: 16px; }
@media (max-width: 699px) { .page-account-now .footer-links { display: none; } } /* the page lists them already */

/* ==========================================================================
   FOOTER
   Desktop: white, under the logo-gradient band: the logo and the line,
   the utility links, social links. Phones: just the links, small and
   centred, above the tab bar.
   ========================================================================== */
.site-footer { position: relative; margin-top: 24px; background: var(--card); color: var(--muted); }
.footer-band { height: 4px; background: var(--brand-gradient); }
.footer-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 20px 40px;
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 32px var(--gutter);
}
.footer-brand { display: flex; flex-direction: column; gap: 4px; margin-right: auto; }
.footer-logo { display: block; width: 128px; }
.footer-brand p { font-size: 14px; color: var(--muted); }
.footer-links { display: flex; flex-wrap: wrap; gap: 4px 24px; }
.footer-links a { padding: 8px 0; color: var(--ink); font-size: 15px; font-weight: 600; }
.footer-links a:hover { color: var(--plum-text); }
.footer-social { display: flex; gap: 2px; }
.footer-social a { display: flex; width: 40px; height: 40px; align-items: center; justify-content: center; border-radius: 50%; color: var(--muted); }
.footer-social a:hover { background: var(--band); color: var(--ink); }
.footer-social svg { width: 20px; height: 20px; }
.footer-fine { max-width: var(--page-max); margin: 0 auto; padding: 0 var(--gutter) 28px; font-size: 13px; color: var(--soft); }
@media (max-width: 699px) {
  .site-footer { margin-top: 0; background: none; }
  .footer-band, .footer-brand { display: none; }
  .footer-inner { flex-direction: column; gap: 8px; padding: 24px 16px 8px; }
  .footer-links { justify-content: center; gap: 0 20px; }
  .footer-links a { padding: 10px 0; font-weight: 500; color: var(--muted); }
  .footer-fine { padding-bottom: 16px; text-align: center; }
}

/* local-only switch for previewing pages as a member (never on the live site) */
.dev-switch {
  position: fixed;
  left: 12px;
  bottom: calc(12px + var(--tabbar-h));
  z-index: 90;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px 4px 4px 10px;
  border-radius: var(--r-pill);
  background: var(--card);
  color: var(--muted);
  box-shadow: 0 4px 18px rgba(0, 0, 0, .18);
  font: 500 12px var(--font-body);
  letter-spacing: .04em;
}
.dev-switch button { border: 0; border-radius: var(--r-pill); padding: 6px 10px; background: var(--band); cursor: pointer; font: inherit; color: var(--ink); }
.dev-switch button.is-on { background: var(--plum); color: var(--white); }

/* ==========================================================================
   PHONE SHELL: THE TAB BAR (under 700px, everyone)
   A floating glass bar: Recipes, Shopping list, Account (round 11: Home,
   Explore, Library, Community, List). The current tab
   is a raised white tile with a plum icon and label. Pages that hide it
   (the recipe page, later) put .no-tabbar on <body>.
   ========================================================================== */
.tabbar { display: none; }
@media (max-width: 699px) {
  .tabbar {
    position: fixed;
    left: 14px;
    right: 14px;
    bottom: calc(14px + env(safe-area-inset-bottom));
    z-index: 45;
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr)); /* round 11: five tabs */
    gap: 2px;
    height: 66px;
    padding: 6px;
    border-radius: var(--r-5);
    background: var(--glass);
    -webkit-backdrop-filter: var(--glass-blur);
    backdrop-filter: var(--glass-blur);
    border: 1px solid var(--glass-edge);
    box-shadow: var(--glass-shadow);
  }
  .tabbar a {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    border-radius: var(--r-4);
    color: var(--muted);
    font-size: 12px;
    font-weight: 600;
  }
  .tabbar a { position: relative; white-space: nowrap; }
  .tabbar svg { width: 23px; height: 23px; }
  /* round 11: List's count of what's left (member.js fills it) */
  .tab-count { position: absolute; top: 3px; left: 50%; margin-left: 5px; min-width: 18px; height: 18px; padding: 0 5px; border-radius: var(--r-pill); background: var(--plum-button); color: var(--white); font: 700 12px/18px var(--font-body); text-align: center; }
  .tab-count:empty { display: none; }
  /* the quick search as a sheet at the top (the tab-head's search button) */
  body.is-signed-in .topbar { display: block; position: fixed; top: 0; left: 0; right: 0; height: 0; z-index: 60; }
  body.is-signed-in .topbar-inner { display: none; }
  body.is-signed-in .panel-search { position: fixed; top: calc(10px + env(safe-area-inset-top)); left: 10px; right: 10px; width: auto; max-height: calc(100vh - 20px - var(--tabbar-h)); }
  body.is-signed-in .hdr-panel:not(.panel-search) { display: none; }
  body.is-signed-in .hdr-scrim { inset: 0; }
  .tabbar a.is-current { background: var(--card); box-shadow: var(--raise); color: var(--plum-text); font-weight: 700; }
  [data-theme="dark"] .tabbar a.is-current { color: var(--white); }
  [data-theme="dark"] .tabbar a.is-current svg { color: var(--plum-icon); }
  body { padding-bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom)); }
  body.no-tabbar { padding-bottom: 0; }
  body.no-tabbar .tabbar { display: none; }
  /* Nov 13 (Dave): recipe pages keep the tab bar, as apps do deeper in a
     tab; it slides down out of the way while you scroll down to read, back
     up when you scroll up (site.js), and leaves in cook mode */
  body.tabbar-auto .tabbar { transition: transform .28s var(--ease), opacity .2s ease; }
  body.tabbar-auto.tabbar-away .tabbar, body.tabbar-auto.is-cooking .tabbar { transform: translateY(calc(100% + 24px)); opacity: 0; pointer-events: none; }
  body.tabbar-auto.is-cooking { padding-bottom: 0; }
}

/* ==========================================================================
   SMALL SCREENS
   ========================================================================== */
@media (max-width: 699px) {
  .hide-sm { display: none !important; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* ==========================================================================
   CAROUSEL ROWS (landing, home)
   Start lined up with the page content, bleed off the right edge,
   snap-scroll. No scrollbar: phones swipe; desktops with a mouse get
   previous/next buttons (site.js adds them, see "rail arrows" below).
   ========================================================================== */
.rail {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 260px;
  gap: 20px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  /* starts lined up with the page, bleeds off the right edge */
  padding: 4px var(--gutter) 14px max(var(--gutter), calc((100vw - var(--page-max)) / 2 + var(--gutter)));
  scroll-padding-left: max(var(--gutter), calc((100vw - var(--page-max)) / 2 + var(--gutter)));
  scrollbar-width: none;
}
.rail::-webkit-scrollbar { display: none; }
.rail-item { scroll-snap-align: start; }

/* rail arrows: 44px white circles centred on the photos at each end of the
   row, desktop with a mouse only. Each click moves one screenful. */
.rail-wrap { position: relative; }
.rail-arrow { display: none; }
@media (hover: hover) and (pointer: fine) {
  .rail-arrow {
    position: absolute;
    z-index: 3;
    top: var(--rail-arrow-top, 110px);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    margin-top: -22px;
    border: 0;
    border-radius: 50%;
    background: var(--white);
    color: var(--ink);
    box-shadow: 0 4px 14px rgba(0, 0, 0, .16), 0 1px 3px rgba(0, 0, 0, .08);
    cursor: pointer;
    transition: opacity .2s var(--ease), transform .15s var(--ease), color .15s;
  }
  .rail-arrow svg { width: 18px; height: 18px; }
  .rail-arrow:hover { color: var(--plum-icon); transform: scale(1.06); }
  .rail-arrow[hidden] { display: flex; opacity: 0; pointer-events: none; visibility: hidden; } /* out of the Tab order too */
  .rail-prev { left: max(4px, calc(max(var(--gutter), calc((100vw - var(--page-max)) / 2 + var(--gutter))) - 22px)); }
  .rail-next { right: max(4px, calc(max(var(--gutter), calc((100vw - var(--page-max)) / 2 + var(--gutter))) - 22px)); } /* never past the window edge */
  .rail-next svg { transform: scaleX(-1); }
}
.rail-date { margin-bottom: 8px; font-size: 12px; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
@media (max-width: 560px) { .rail { grid-auto-columns: 158px; gap: 12px; } }

/* ==========================================================================
   ACCOUNT PAGES (Outseta forms) AND TEXT PAGES
   ========================================================================== */
.account-page { min-height: calc(100vh - var(--header-h) - 240px); padding: 48px 16px 72px; }
.account-card { max-width: 480px; margin: 0 auto; padding: 36px 32px 30px; background: var(--card); border-radius: var(--radius-card); box-shadow: var(--raise); }
.account-card-wide { max-width: 640px; }
.account-title { margin: 6px 0 8px; font-family: var(--font-display); font-weight: 700; font-size: 32px; }
.account-lead { color: var(--muted); font-size: 16px; line-height: 1.55; }
.account-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 22px; }
.account-alt { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--line); color: var(--muted); text-align: center; }
.account-alt a { font-weight: 700; }
/* Nov 11: on the way from a gathering's invite (sign-up.php) */
.account-invite { margin: 0 0 20px; padding: 14px 16px; border-radius: var(--r-3); background: var(--well-bg); box-shadow: var(--well); color: var(--ink); line-height: 1.5; }
.account-invite p { margin: 0; }
.account-invite a { font-weight: 700; }
.account-invite b { font-weight: 700; } /* Nov 11 (impeccable pass): was a small capitals label above the sentence */
.account-exists { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 10px 14px; margin: 10px 0 18px; padding: 14px 16px; border-radius: var(--r-3); background: var(--well-bg); box-shadow: var(--well); font-weight: 600; text-align: center; }
.outseta-embed { margin-top: 20px; min-height: 120px; }
/* Outseta's embedded forms, dressed in Cocamoo's own buttons and fields:
   plum for the one action, our inputs, and no second "Login" heading under
   ours (site.js also renames its "Login" button to "Sign in"). */
.outseta-embed [style*="--accent-color"] { --accent-color: #9E2E57 !important; --font-family: "Cabin" !important; }
.outseta-embed [data-o-auth] h1 { display: none; }
.outseta-embed .o--Widget--widgetBody { padding: 0 !important; }
/* Outseta paints its own white panel; ours is the card behind it, so dark
   mode's light labels were white on white (harden, Oct 26) */
.outseta-embed .o--Widget--widget, .outseta-embed .o--Widget--widgetBody { background: transparent !important; border: 0 !important; box-shadow: none !important; }
.outseta-embed .o--Label--label { font: 600 15px var(--font-body) !important; color: var(--ink) !important; }
.outseta-embed .o--Label--required::after { color: var(--muted) !important; } /* not Outseta's red */
/* fields are wells, like search and every input on the site */
.outseta-embed .o--Input--input {
  min-height: 48px !important;
  padding: 0 14px !important;
  border: 0 !important;
  border-radius: var(--r-4) !important;
  background: var(--well-bg) !important;
  box-shadow: var(--well) !important;
  color: var(--ink) !important;
  font: 400 16px var(--font-body) !important;
}
.outseta-embed .o--Input--input:focus { box-shadow: var(--well), 0 0 0 2px var(--plum-icon) !important; outline: none !important; }
.outseta-embed .o--Button--btn {
  min-height: 48px !important;
  border-radius: var(--r-3) !important;
  font: 600 16px var(--font-body) !important;
}
.outseta-embed .o--Button--buttonStyle-solid:not(.o--Button--buttonType-cancel) { width: 100%; background: var(--plum) !important; border-color: var(--plum) !important; color: var(--white) !important; }
/* "Sign in with Google": a raised secondary button, not a second plum one */
.outseta-embed .o--Button--btn.o--SignInWithGoogle--btnGoogleSignin { background: var(--card) !important; border: 0 !important; box-shadow: var(--raise) !important; color: var(--ink) !important; }
.outseta-embed .o--Button--buttonStyle-solid:not(.o--Button--buttonType-cancel):hover:not([disabled]) { background: var(--plum-dark) !important; }
.outseta-embed .o--Button--buttonType-cancel { background: var(--white) !important; border: 1.5px solid var(--ring) !important; color: var(--ink) !important; }
.outseta-embed a { color: var(--plum-icon) !important; }
/* Nov 11: our "Sign in with it" under Outseta's "already exists" error
   (site.js) sits inside Outseta's form, whose styles would otherwise win */
.outseta-embed .account-exists { background: var(--well-bg) !important; box-shadow: var(--well) !important; color: var(--ink) !important; font: 600 15px/1.4 var(--font-body) !important; }
.outseta-embed .account-exists a.btn { background: var(--card) !important; box-shadow: var(--raise), 0 0 0 1px var(--line) !important; border: 0 !important; outline: none !important; color: var(--ink) !important; text-decoration: none !important; font: 600 15px/1 var(--font-body) !important; }
.outseta-embed .account-exists a.btn:hover { color: var(--plum-text) !important; }
.outseta-embed .account-exists a.btn:focus-visible { box-shadow: var(--raise), 0 0 0 2px var(--plum-icon) !important; }
@media (max-width: 560px) { .account-card { padding: 28px 20px 24px; } }

/* Brevo mailing list form, restyled */
.brevo { margin-top: 18px; text-align: left; background: none !important; }
.brevo #sib-container { padding: 0; max-width: none; background: none; }
.brevo .brevo-row { display: grid; grid-template-columns: 1fr 1fr; gap: 0 14px; }
.brevo .sib-form-block { padding: 8px 0; }
.brevo .entry__label { display: block; margin-bottom: 6px; font: 700 14px var(--font-body); color: var(--ink); }
.brevo .input { width: 100%; height: 46px; padding: 0 12px; border: 1.5px solid var(--ring); border-radius: var(--r-3); font: 400 16px var(--font-body); color: var(--ink); background: var(--white); }
.brevo .input:focus { outline: none; border-color: var(--plum-icon); box-shadow: 0 0 0 3px var(--plum-glow); }
.brevo .brevo-fine { margin: 8px 0; font-size: 13px; line-height: 1.5; color: var(--muted); }
.brevo .brevo-optin { display: flex; gap: 10px; align-items: flex-start; font-size: 14px; color: var(--ink); cursor: pointer; }
.brevo .brevo-optin input { margin-top: 3px; accent-color: var(--plum-icon); width: 18px; height: 18px; }
.brevo .brevo-optin .checkbox { display: none; }
.brevo .sib-form-block__button { margin-top: 8px; width: 100%; min-height: 50px; font-size: 16px; }
.brevo .input--hidden { display: none !important; }
.brevo .brevo-msg { display: none; margin-bottom: 12px; padding: 12px 14px; border-radius: var(--r-3); font-size: 15px; }
.brevo .brevo-error { background: var(--error-tint); color: var(--error-ink); }
.brevo .brevo-success { background: var(--ok-tint); color: var(--ok-ink); }
.brevo .entry__error { color: var(--error); font-size: 13px; }
@media (max-width: 480px) { .brevo .brevo-row { grid-template-columns: 1fr; } }

.text-page { padding: 48px 0 72px; }
.text-title { margin: 8px 0 16px; font-family: var(--font-display); font-weight: 700; font-size: clamp(32px, 5vw, 44px); line-height: 1.1; }
.text-lead { font-size: 19px; color: var(--muted); }
.prose { font-size: 17px; line-height: 1.7; }
.prose h2 { margin: 28px 0 8px; font-family: var(--font-display); font-size: 24px; }
.prose p { margin: 0 0 14px; }
.prose ul { margin: 0 0 16px; padding-left: 22px; }
.prose li { margin: 4px 0; }
.about-photo { width: 100%; margin: 10px 0 22px; border-radius: var(--r-4); aspect-ratio: 4 / 3; object-fit: cover; object-position: 50% 4%; background: var(--band); }
/* Nov 12 (Dave): the photo is a tall portrait, and a wide crop from its
   middle cut his head off. 4:3 from near the top keeps every face. */
.text-search { display: flex; gap: 8px; margin: 22px 0; max-width: 520px; }
.text-search input { flex: 1 1 auto; min-width: 0; height: 46px; padding: 0 14px; border: 1.5px solid var(--ring); border-radius: var(--r-3); font: inherit; }
.brevo .brevo-msg.sib-form-message-panel--active { display: block; }

/* ==========================================================================
   AUDIT FIXES (Oct 26, 2026): small things the full audit found
   ========================================================================== */
/* placeholders: the browser's grey was 3.8:1 in the wells */
input::placeholder, textarea::placeholder { color: var(--muted); opacity: 1; }
/* the logo: a full 44px to tap */
.topbar-logo { position: relative; }
.topbar-logo::after { content: ""; position: absolute; inset: -14px -6px; }
/* menus and lists you move through with the keyboard: a plum ring, not
   just the faint grey hover */
.pick-opt:focus-visible, .lib-opt:focus-visible, .menu-link:focus-visible, .more-item:focus-visible, .rc-menu-row:focus-visible,
.qs-item.is-active, .qs-item:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--plum-icon); }
/* body text: no single word left alone on the last line */
p, li { text-wrap: pretty; }
/* headings and card names: lines of even length, no lone last word (typeset, Oct 26) */
h1, h2, h3, .card-name, .row-name, .lib-name { text-wrap: balance; }

/* Asked to delete their account, signed in again within 30 days (step 7) */
.deletion-banner { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px 16px; max-width: var(--page-max); margin: 12px auto 0; padding: 12px 18px; border-radius: var(--r-4); background: var(--error-tint); color: var(--error-ink); }
@media (min-width: 700px) { .deletion-banner { margin: 16px var(--gutter) 0; } }
@media (min-width: 1384px) { .deletion-banner { margin-left: auto; margin-right: auto; } }

/* ==========================================================================
   DARK MODE FIXES (step 7 audit, Oct 26): pieces that were white or Ink by
   design in light and need their own dark look
   ========================================================================== */
/* the toast: a dark card in dark mode (it was Ink, which turns near-white) */
[data-theme="dark"] .cm-toast { background: #2A2A31; box-shadow: var(--glass-shadow); }
/* carousel arrows: a card, not a white disc */
[data-theme="dark"] .rail-arrow { background: var(--card); box-shadow: var(--raise); color: var(--ink); }
/* forms from Outseta and the mailing list */
[data-theme="dark"] .outseta-embed .o--Button--buttonType-cancel, [data-theme="dark"] .brevo .input { background: var(--card) !important; }
/* the light paper ribbon keeps its own plum (the dark icon plum is for dark surfaces) */
[data-theme="dark"] .cm-row { --plum-icon: #9E2E57; }

/* The avatar: the person's profile photo filling the round button, their
   initial when there's no photo (header.php; site.js keeps it current) */
.avatar-btn { position: relative; overflow: hidden; }
.avatar-btn .avatar-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
.avatar-btn.has-photo [data-member-initial] { visibility: hidden; }

/* ---- Outseta's profile panel (/dashboard/your-profile, account/profile.php),
   laid out the Cocamoo way (Oct 27). Outseta pulls its panel outward with
   negative margins that our zero padding didn't cancel, so its menu rode up
   into the page title: those go. Then its tabs become a menu like ours
   (grey hover, solid plum for the one you're on), and each field (Name,
   Email, Password) a row: label and value on the left, a small raised
   Change button on the right. ---- */
.account-card-profile { max-width: 860px; }
/* Outseta's own layers stay inside the panel, so the phone tab bar passes over its buttons (Nov 10) */
.outseta-profile { position: relative; z-index: 0; isolation: isolate; }
.outseta-profile .o--App--profileWidget { max-width: none !important; border: 0 !important; }
.outseta-profile .o--App--widgetBody { margin: 0 !important; gap: 0 32px; }
/* (clipped to its own column: an edit form's footer line reached out past
   it, under the menu; the 4px of room keeps the inputs' focus rings) */
.outseta-profile .o--App--widgetContent { padding: 4px 4px 28px !important; margin: -4px -4px 0; min-width: 0; overflow: hidden; } /* Nov 10: 28px below, so button shadows aren't cut off (no pull-back: it hid the line under the panel) */
.outseta-profile .o--App--widgetContent h1 { margin: 2px 0 14px !important; font: 700 22px var(--font-display) !important; color: var(--ink) !important; }
/* the tabs: Outseta draws each tab's highlight as a square layer behind
   the link (li::before); that goes, and the link itself is the row */
.outseta-profile .o--NavDesktop--navDesktop { min-width: 180px !important; padding: 0 !important; border: 0 !important; }
.outseta-profile .o--NavDesktop--navDesktop li { margin: 0 0 4px !important; }
.outseta-profile .o--NavDesktop--navDesktop li::before { display: none !important; }
.outseta-profile .o--NavDesktop--navDesktop li a {
  display: flex !important;
  align-items: center;
  min-height: 44px;
  padding: 0 14px !important;
  border-radius: var(--r-3);
  color: var(--ink) !important;
  font: 600 16px var(--font-body) !important;
  text-decoration: none !important;
}
.outseta-profile .o--NavDesktop--navDesktop li a:hover { background: var(--well-bg); }
.outseta-profile .o--NavDesktop--navDesktop li.o--NavDesktop--active a { background: var(--well-bg); color: var(--plum-text) !important; font-weight: 700 !important; } /* grey, plum words (Nov 10) */
/* each field: a row, its Change button beside it */
.outseta-profile .o--SectionGroup--sectionGroup {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 16px;
  row-gap: 2px;
  margin: 0 !important;
  padding: 16px 0;
  border-top: 1px solid var(--sep);
}
.outseta-profile .o--SectionGroup--sectionGroup > * { grid-column: 1; margin: 0 !important; }
.outseta-profile .o--SectionGroup--sectionGroup > .o--Button--btn { grid-column: 2; grid-row: 1 / span 3; justify-self: end; }
.outseta-profile .o--SectionGroup--sectionGroup img { width: 72px; height: 72px; border-radius: 50%; object-fit: cover; }
/* Change name / Change email / Remove profile photo: small raised pill
   buttons (Outseta makes them "solid" + "cancel" buttons, with its own
   padding that pushed the words up) */
.outseta-profile .o--App--widgetContent .o--Button--btn.o--Button--buttonType-cancel,
.outseta-profile .o--App--widgetContent .o--Button--btn:not(.o--Button--buttonStyle-solid):not(.o--Button--btn-link) {
  display: inline-flex !important;
  align-items: center;
  justify-content: center;
  width: auto !important;
  min-height: 40px !important;
  margin: 0 !important;
  padding: 0 16px !important;
  border: 0 !important;
  border-radius: var(--r-pill) !important;
  background: var(--card) !important;
  box-shadow: var(--raise), 0 0 0 1px var(--line) !important;
  color: var(--ink) !important;
  font: 600 15px/1 var(--font-body) !important;
  white-space: nowrap;
}
/* hover, press and keyboard focus as our raised buttons (.btn-raised):
   it lifts, then sets down. Outseta colours the label inside the button
   itself, so the label follows the button's colour (Dave, Oct 27). */
.outseta-profile .o--App--widgetContent .o--Button--btn.o--Button--buttonType-cancel,
.outseta-profile .o--App--widgetContent .o--Button--btn:not(.o--Button--buttonStyle-solid):not(.o--Button--btn-link) { cursor: pointer; transition: box-shadow .15s, color .15s, transform .1s !important; }
.outseta-profile .o--App--widgetContent .o--Button--btn.o--Button--buttonType-cancel:hover,
.outseta-profile .o--App--widgetContent .o--Button--btn:not(.o--Button--buttonStyle-solid):not(.o--Button--btn-link):hover {
  background: var(--card) !important;
  box-shadow: 0 1px 2px rgba(24, 24, 27, .06), 0 12px 26px rgba(24, 24, 27, .1), inset 0 1px 0 rgba(255, 255, 255, .9), 0 0 0 1px var(--ring) !important;
  color: var(--plum-text) !important;
}
.outseta-profile .o--App--widgetContent .o--Button--btn.o--Button--buttonType-cancel:active,
.outseta-profile .o--App--widgetContent .o--Button--btn:not(.o--Button--buttonStyle-solid):not(.o--Button--btn-link):active {
  transform: translateY(1px) scale(.99);
  box-shadow: 0 1px 1px rgba(24, 24, 27, .12), 0 0 0 1px var(--ring) !important;
}
.outseta-profile .o--App--widgetContent .o--Button--btn.o--Button--buttonType-cancel:focus-visible,
.outseta-profile .o--App--widgetContent .o--Button--btn:not(.o--Button--buttonStyle-solid):not(.o--Button--btn-link):focus-visible { outline: 2px solid var(--plum-icon) !important; outline-offset: 2px; }
.outseta-profile .o--App--widgetContent .o--Button--btn.o--Button--buttonType-cancel .o--Button--children,
.outseta-profile .o--App--widgetContent .o--Button--btn.o--Button--buttonType-cancel .o--Button--children *,
.outseta-profile .o--App--widgetContent .o--Button--btn:not(.o--Button--buttonStyle-solid):not(.o--Button--btn-link) .o--Button--children { color: inherit !important; }
.outseta-profile .o--App--widgetContent .o--Button--btn .o--Button--children { margin: 0 !important; padding: 0 !important; line-height: 1 !important; }
/* the edit forms' Cancel + Save: the margin Outseta kept between them was
   zeroed with the pills' above, so Cancel gets room on its right again.
   (Each button sits in its own wrapper, so "Cancel + Save" never matched;
   this works however they're wrapped. The Change buttons in the field
   rows keep their edge.) */
.outseta-profile .o--App--widgetContent .o--Button--btn.o--Button--buttonType-cancel:not(.o--SectionGroup--sectionGroup .o--Button--btn) { margin-right: 12px !important; }
@media (prefers-reduced-motion: reduce) { .outseta-profile .o--App--widgetContent .o--Button--btn { transition: none !important; transform: none !important; } }
.outseta-profile .o--Button--btn .o--Button--children { font-size: inherit !important; }
@media (max-width: 699px) {
  .outseta-profile .o--App--widgetBody { display: block !important; }
}

/* ==========================================================================
   UP NEXT (round 8, step 3; js/upnext.js): Explore's row and Dinner's
   sorted, the Library's shelf, and the Made it? sheet (R8-U-Explore,
   R8-U-Library, R8-U-MadeIt). Plum for what the person does.
   ========================================================================== */
.un-row { display: flex; gap: 14px; margin: 0 calc(var(--gutter) * -1); padding: 4px var(--gutter) 10px; overflow-x: auto; scroll-snap-type: x mandatory; scroll-padding-inline: var(--gutter); scrollbar-width: none; } /* snapping lines cards up with the gutter, not the screen's edge (walkthrough, Oct 29) */
.un-row::-webkit-scrollbar { display: none; }
.un-card, .un-shelf-card { position: relative; flex: 0 0 auto; width: 200px; scroll-snap-align: start; }
.un-link { display: flex; flex-direction: column; gap: 4px; color: var(--ink); text-decoration: none; }
.un-photo { position: relative; display: block; overflow: hidden; aspect-ratio: 3 / 2; border-radius: var(--r-4); background: var(--cat, var(--band)); box-shadow: var(--raise); }
.un-photo img { width: 100%; height: 100%; object-fit: cover; }
.un-name { margin-top: 6px; font-size: 16px; font-weight: 600; line-height: 1.3; }
.un-link:hover .un-name { color: var(--plum-text); }
.un-sub { color: var(--muted); font-size: 14px; }
/* "Made it?" sits on the photo (Explore) */
.un-ask { position: absolute; left: 10px; top: calc(200px / 1.5 - 44px); min-height: 34px; padding: 0 12px; border: 0; border-radius: var(--r-pill); background: var(--glass); -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur); box-shadow: var(--glass-shadow); color: var(--plum-text); font: 700 14px var(--font-body); cursor: pointer; }
.un-ask::before { content: ""; position: absolute; inset: -5px; } /* 44px to tap */
/* the Library's shelf: raised cards, the state as a chip */
.un-shelf-card { display: flex; flex-direction: column; gap: 10px; width: 220px; padding: 8px 8px 14px; border-radius: var(--r-5); background: var(--card); box-shadow: var(--raise); }
.un-shelf-card .un-name { margin: 2px 6px 0; }
.un-chip { align-self: flex-start; display: inline-flex; align-items: center; min-height: 34px; margin: 0 6px; padding: 0 12px; border: 0; border-radius: var(--r-pill); background: var(--band); color: var(--muted); font: 600 14px var(--font-body); }
.un-chip.is-ask { background: var(--card); box-shadow: var(--raise), 0 0 0 1px var(--line); color: var(--plum-text); cursor: pointer; } /* Nov 10: a raised chip, not a main button */
/* Nov 10: the ✕ that takes a card off Up next, glass over the photo's corner */
.un-x { position: absolute; top: 6px; right: 6px; z-index: 2; display: grid; place-items: center; width: 32px; height: 32px; border: 0; border-radius: 50%; background: var(--glass); -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur); box-shadow: var(--glass-shadow); color: var(--ink); cursor: pointer; }
.un-x::before { content: ""; position: absolute; inset: -6px; } /* 44px to tap */
.un-x svg { width: 16px; height: 16px; }
.un-x:hover { color: var(--plum-text); }
.un-x:focus-visible { outline: 2px solid var(--plum-icon); outline-offset: 2px; }
.un-shelf-card .un-x { top: 14px; right: 14px; }
.un-rail-row { position: relative; padding-right: 36px; }
.un-rail-row .un-x { top: 50%; right: 0; transform: translateY(-50%); background: none; box-shadow: none; -webkit-backdrop-filter: none; backdrop-filter: none; color: var(--soft); }
.un-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
.un-head-h { font: 700 26px var(--font-display); }
.un-head-aside { color: var(--muted); font-size: 15px; }
.un-head a { color: var(--plum-text); font-weight: 700; text-decoration: none; }

/* Dinner's sorted: the recipe's photo, the greeting and its name over it.
   Oct 29 (Dave chose "A, the ticket"): the photo beside the words, the name
   in ink on the card (never over the photo), one meta line, Start cooking
   with Not tonight as a quiet link. Phones: photo and words side by side,
   the buttons in a row under both. */
.diet-ask, .sorted { margin-bottom: 22px; }
.sorted {
  display: grid;
  grid-template-columns: 108px minmax(0, 1fr);
  grid-template-areas: "photo words" "acts acts";
  overflow: hidden;
  border-radius: var(--r-5);
  background: var(--card);
  box-shadow: var(--raise);
}
.sorted[hidden] { display: none; }
.sorted-photo { grid-area: photo; min-height: 124px; background: var(--cat, var(--plum-dark)) center / cover no-repeat; }
.sorted-words { grid-area: words; display: flex; flex-direction: column; gap: 5px; min-width: 0; padding: 14px 16px 12px; }
.sorted-hello { margin: 0; color: var(--muted); font-size: 13px; }
.sorted-name { margin: 0; font: 700 19px/1.22 var(--font-display); }
.sorted-name a { color: var(--ink); text-decoration: none; }
.sorted-name a:hover { color: var(--plum-text); }
.sorted-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 7px; margin: 0; color: var(--muted); font-size: 14px; }
.sorted-meta span + span::before { content: "·"; margin-right: 7px; }
.sorted-meta svg { width: 14px; height: 14px; margin-right: 3px; color: var(--plum-icon); vertical-align: -2px; }
.sorted-acts { grid-area: acts; display: flex; align-items: center; gap: 20px; padding: 12px 16px 14px; border-top: 1px solid var(--line); }
.sorted-acts .btn-plum { flex: 1 1 auto; }
.sorted-acts .text-link { min-height: 44px; padding: 0 4px; color: var(--muted); font-weight: 600; }
.sorted-acts .text-link:hover { color: var(--plum-text); }
@media (min-width: 700px) {
  .sorted { grid-template-columns: minmax(200px, 36%) minmax(0, 1fr); grid-template-areas: "photo words" "photo acts"; grid-template-rows: 1fr auto; }
  .sorted-photo { min-height: 200px; }
  .sorted-words { justify-content: flex-end; gap: 7px; padding: 24px 26px 6px; }
  .sorted-hello { font-size: 14px; }
  .sorted-name { font-size: 26px; }
  .sorted-acts { padding: 12px 26px 22px; border-top: 0; }
  .sorted-acts .btn-plum { flex: 0 0 auto; }
}

/* the one-time diet question (R8-U-Diet), asked once. Nov 12 (round 10
   carry-over): a light raised card (was the only black card on Explore);
   raised chips, plum only once picked; the answer button raised, not filled */
.diet-ask { padding: 22px 20px 20px; border-radius: var(--r-5); background: var(--card); box-shadow: var(--raise); color: var(--ink); }
.diet-ask-hello { color: var(--muted); font-size: 15px; }
.diet-ask-h { margin: 8px 0 16px; font: 700 clamp(24px, 5vw, 30px)/1.2 var(--font-display); }
.diet-ask-chips { display: flex; flex-wrap: wrap; gap: 10px; }
.diet-ask-chip { min-height: 44px; padding: 0 18px; border: 0; border-radius: var(--r-pill); background: var(--card); box-shadow: var(--raise), 0 0 0 1px var(--line); color: var(--ink); font: 600 16px var(--font-body); cursor: pointer; }
.diet-ask-chip:hover { color: var(--plum-text); }
.diet-ask-chip[aria-pressed="true"] { background: var(--plum-button); box-shadow: var(--plum-shadow); color: var(--white); }
.diet-ask-go { width: 100%; min-height: 52px; margin-top: 16px; border: 0; border-radius: var(--r-pill); background: var(--card); box-shadow: var(--raise), 0 0 0 1px var(--line); color: var(--ink); font: 700 17px var(--font-body); cursor: pointer; }
.diet-ask-go:hover { color: var(--plum-text); }
.diet-ask-note { margin-top: 14px; color: var(--muted); font-size: 14px; line-height: 1.45; }

/* the Made it? sheet */
.un-sheet {
  position: fixed;
  z-index: 81;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  flex-direction: column;
  gap: 16px;
  max-height: 92vh;
  overflow-y: auto;
  padding: 10px 22px calc(20px + env(safe-area-inset-bottom));
  border-radius: var(--r-5) var(--r-5) 0 0;
  background: var(--ground);
  box-shadow: 0 -20px 50px rgba(0, 0, 0, .18);
  animation: sheet-in-up .24s var(--ease);
}
.un-sheet[hidden] { display: none; }
@media (min-width: 700px) {
  .un-sheet { left: 50%; right: auto; bottom: auto; top: 6vh; width: min(520px, 92vw); transform: translateX(-50%); border-radius: var(--r-5); animation: none; }
  .un-sheet-grip { display: none; }
}
.un-sheet-grip { align-self: center; width: 36px; height: 5px; border-radius: 3px; background: var(--ring); }
.un-sheet-head { display: flex; align-items: center; gap: 16px; }
.un-sheet-head .un-photo { flex: 0 0 auto; width: 64px; aspect-ratio: 1; border-radius: var(--r-4); }
.un-sheet-when { color: var(--muted); font-size: 15px; }
.un-sheet-h { font: 700 24px/1.2 var(--font-display); }
.un-stars { display: flex; justify-content: center; gap: 10px; }
.un-star { display: grid; width: 52px; height: 52px; place-items: center; border: 0; border-radius: var(--r-4); background: var(--card); box-shadow: var(--raise), 0 0 0 1px var(--line); color: var(--ring); cursor: pointer; }
.un-star svg { width: 24px; height: 24px; fill: currentColor; }
.un-star.is-on { background: var(--plum-button); box-shadow: var(--plum-shadow); color: var(--white); }
.un-sheet-label { margin-bottom: -6px; color: var(--muted); font-weight: 700; }
.un-again { display: flex; }
.un-again button { flex: 1 1 0; justify-content: center; padding: 0 8px; }
.un-note { width: 100%; min-height: 96px; padding: 14px 16px; border: 0; border-radius: var(--r-4); background: var(--well-bg); box-shadow: var(--well); color: var(--ink); font: 400 16px/1.45 var(--font-body); resize: vertical; }
.un-note:focus { outline: none; box-shadow: var(--well), 0 0 0 2px var(--plum-glow); }
.un-private { display: flex; align-items: center; gap: 10px; min-height: 44px; font-size: 15px; cursor: pointer; }
.un-private input { position: absolute; opacity: 0; }
.un-private input:checked + .check-box { background: var(--plum-button); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .3), 0 3px 8px rgba(158, 46, 87, .3); }
.un-private input:checked + .check-box::after { content: ""; position: absolute; left: 7px; top: 3px; width: 5px; height: 10px; border: solid var(--white); border-width: 0 2.4px 2.4px 0; transform: rotate(45deg); }
.un-private input:focus-visible + .check-box { outline: 2px solid var(--plum-icon); outline-offset: 2px; }
.un-sheet-foot { margin-top: -8px; color: var(--muted); font-size: 14px; }
/* Your photo (own and AI recipes, R8-U-MadeIt, Oct 29): a square tile, a plum
   tick on it while it's going to be the main photo, the box beside it */
.un-photo-pick { display: flex; align-items: center; gap: 16px; }
.un-photo-tile { position: relative; flex: 0 0 auto; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; width: 96px; height: 96px; overflow: hidden; border-radius: var(--r-5); background: var(--well-bg); box-shadow: var(--well); color: var(--plum-text); font-size: 13px; font-weight: 700; cursor: pointer; }
.un-photo-tile svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; }
.un-photo-tile:focus-within { outline: 2px solid var(--plum-icon); outline-offset: 2px; }
.un-photo-tile.has-photo { box-shadow: var(--raise); }
.un-photo-tile img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.un-photo-tick { position: absolute; top: 6px; right: 6px; display: grid; place-items: center; width: 26px; height: 26px; border-radius: 50%; background: var(--plum-button); box-shadow: 0 3px 8px rgba(158, 46, 87, .3); color: var(--white); }
.un-photo-tick svg { width: 14px; height: 14px; stroke-width: 3; stroke-linejoin: round; }
.un-photo-text { min-width: 0; }
.un-photo-h { font-size: 17px; font-weight: 700; }
.un-photo-text .un-sheet-foot { margin-top: 2px; }
.un-sheet-error { color: var(--plum-text); font-weight: 600; }
.un-sheet-acts { display: flex; flex-direction: column; align-items: center; gap: 4px; margin-top: 4px; }
.un-later { min-height: 44px; border: 0; background: none; color: var(--muted); font: 600 15px var(--font-body); cursor: pointer; }
.un-scrim { position: fixed; inset: 0; z-index: 80; background: var(--scrim); }
.un-scrim[hidden] { display: none; }
@media (prefers-reduced-motion: reduce) { .un-sheet { animation: none; } .un-row { scroll-behavior: auto; } }

/* ==========================================================================
   COCAMOO'S AI (round 8, step 4: R8-AI-Style, R8-AI-Pot; ai.js)
   Solid gradient for AI actions and badges; a gradient ring with the soft
   wash for AI areas; never on checkmarks, step numbers, stars, the shopping
   list or anything the person does themselves (those stay plum). Hidden
   everywhere while the AI is off (body[data-ai="off"], config 'ai.mode').
   ========================================================================== */
body[data-ai="off"] [data-ai-only] { display: none !important; }
/* Oct 29 (Dave chose option A, "same pill, AI ink"): the AI button is the
   plum pill exactly (height, padding, type, pressed), filled with the AI
   gradient fitted to the button (100deg, so a wide one never turns into a
   banner) and a glow as soft as plum's. It's for committing an AI action
   (Write it, Save and work out the rest). Ways *into* AI are .ai-ring-btn:
   a white pill with the gradient ring and an AI-ink sparkle. */
.ai-btn { display: inline-flex; align-items: center; justify-content: center; gap: 7px; min-height: var(--btn-md); padding: 0 18px; border: 0; border-radius: var(--r-pill); background: linear-gradient(100deg, #1E7299 0%, #4A5BA0 40%, #7B3F8E 72%, #A8336E 100%); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .3), inset 0 -1px 0 rgba(0, 0, 0, .2), 0 6px 14px rgba(74, 91, 160, .28); color: var(--white); font: 600 15px/1 var(--font-body); text-decoration: none; white-space: nowrap; cursor: pointer; transition: filter .15s, transform .1s; }
[data-theme="dark"] .ai-btn { background: linear-gradient(100deg, #2A86AE 0%, #5A6BB3 40%, #8C4E9F 72%, #B8417C 100%); }
@media (min-width: 700px) { .ai-btn { padding: 0 16px; font-size: 14px; } }
.ai-btn.btn-lg { min-height: var(--btn-lg); padding: 0 24px; font-size: 16px; font-weight: 700; }
.ai-btn svg { width: 16px; height: 16px; }
.ai-btn:hover { color: var(--white); filter: brightness(1.06); }
.ai-btn:active { transform: translateY(1px); box-shadow: inset 0 2px 4px rgba(0, 0, 0, .25); filter: none; }
/* a way into AI: the raised white pill, ringed in the gradient */
.ai-ring-btn { border: 1.5px solid transparent; background: linear-gradient(var(--card), var(--card)) padding-box, var(--ai-ring) border-box; box-shadow: var(--raise); color: var(--ink); }
.ai-ring-btn svg { color: var(--ai-ink); }
.ai-ring-btn:hover { color: var(--ai-ink); }
.ai-btn:disabled { filter: saturate(.6); opacity: .75; cursor: progress; }
.ai-area { border: 1.5px solid transparent; border-radius: var(--r-5); background: var(--ai-soft) padding-box, var(--ai-ring) border-box; }
.ai-label { display: inline-flex; align-items: center; gap: 8px; color: var(--ai-ink); font-weight: 700; }
/* Ask Cocamoo strip (partials/ask-strip.php, Nov 10): under Explore's finder and atop the Library */
.ask-strip { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 20px; margin: 16px 0; padding: 14px 18px; }
.ask-strip-words { flex: 1 1 260px; min-width: 0; display: grid; gap: 2px; }
.ask-strip-line { font-size: 15px; line-height: 1.45; color: var(--muted); }
.ask-strip-btn { flex: 0 0 auto; }
@media (max-width: 699px) { .ask-strip-btn { width: 100%; justify-content: center; } }
/* Nov 12, later: two buttons (Write me a recipe, From a photo); side by side on phones too */
.ask-strip-btns { display: flex; flex-wrap: wrap; gap: 10px; flex: 0 0 auto; }
.ask-strip-btn svg { width: 16px; height: 16px; }
@media (max-width: 699px) { .ask-strip-btns { flex: 1 1 100%; } .ask-strip-btns .ask-strip-btn { flex: 1 1 0; width: auto; padding-inline: 12px; } }
.ai-label svg { width: 18px; height: 18px; }
.ai-badge { display: inline-flex; align-items: center; gap: 6px; min-height: 30px; padding: 0 12px; border-radius: var(--r-pill); background: var(--ai); color: var(--white); font: 700 14px var(--font-body); }
.ai-badge svg { width: 14px; height: 14px; }
.ai-tag { display: inline-flex; padding: 1px 8px; border: 1px solid transparent; border-radius: var(--r-2); background: var(--ai-soft) padding-box, var(--ai-ring) border-box; color: var(--ai-ink); font-size: 12px; font-weight: 700; }
.ai-shimmer { display: block; height: 10px; border-radius: var(--r-1); background: var(--ai-shimmer); background-size: 200% 100%; animation: ai-shimmer 1.6s ease-in-out infinite; }
@keyframes ai-shimmer { 0%, 100% { background-position: 0 0; } 50% { background-position: 100% 0; } }

/* the small loader: a spinning ring with a spark ("Writing it…") */
.ai-spin { position: relative; display: inline-grid; flex: 0 0 auto; width: 22px; height: 22px; place-items: center; }
.ai-spin::before { content: ""; position: absolute; inset: 0; border-radius: 50%; background: conic-gradient(from 0deg, rgba(30, 114, 153, 0), #1E7299 30%, #7B3F8E 65%, #A8336E); -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 3px)); mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 3px)); animation: cpspin 1.1s linear infinite; }
.ai-pill { display: inline-flex; align-items: center; gap: 10px; min-height: 40px; padding: 0 16px 0 10px; border-radius: var(--r-pill); background: var(--card); box-shadow: var(--raise); color: var(--ai-ink); font: 600 15px var(--font-body); }

/* the big loader: sparkle steam from a pot, a changing status line */
.ai-pot { display: flex; flex-direction: column; align-items: center; gap: 18px; padding: 24px 0; text-align: center; }
.ai-pot-disc { position: relative; display: grid; width: 168px; height: 168px; place-items: center; border-radius: 50%; background: var(--card); box-shadow: var(--raise); }
.ai-pot-disc::before { content: ""; position: absolute; inset: -6px; border-radius: 50%; background: conic-gradient(from 0deg, rgba(30, 114, 153, 0), #1E7299 25%, #4A5BA0 50%, #7B3F8E 75%, #A8336E); -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 5px), #000 calc(100% - 5px)); mask: radial-gradient(farthest-side, transparent calc(100% - 5px), #000 calc(100% - 5px)); animation: cpspin 1.8s linear infinite; }
[data-theme="dark"] .ai-pot-disc::before { background: conic-gradient(from 0deg, rgba(63, 164, 207, 0), #3FA4CF 25%, #6F7FD0 50%, #A869BF 75%, #D3609A); }
.ai-pot-pan { position: relative; display: block; width: 96px; height: 96px; margin-top: 30px; color: var(--ink); animation: cpbob 2.6s ease-in-out infinite; } /* Nov 13: the solid set's pot (ai/ai-pot.svg, a mask) */
.ai-steam { position: absolute; left: 50%; top: 64px; width: 0; height: 0; }
.ai-steam .st { position: absolute; left: -7px; top: 0; width: 14px; height: 14px; opacity: 0; animation: cpsteam 2.6s ease-out infinite; }
.ai-steam .st.l { margin-left: -14px; animation-name: cpsteamL; }
.ai-steam .st.r { margin-left: 14px; animation-name: cpsteamR; }
.ai-steam .st svg { width: 100%; height: 100%; fill: currentColor; }
.ai-steam .st .ic-solid { display: block; width: 100%; height: 100%; }
.ai-steam .st.w { width: 18px; height: 26px; margin-top: -6px; animation-name: cpwisp; } /* steam wisps rise without turning */
.ai-steam .st.w.l { animation-name: cpwispL; } .ai-steam .st.w.r { animation-name: cpwispR; }
.ai-steam .c1 { color: #1E7299; } .ai-steam .c2 { color: #4A5BA0; } .ai-steam .c3 { color: #7B3F8E; } .ai-steam .c4 { color: #A8336E; }
[data-theme="dark"] .ai-steam .c1 { color: #3FA4CF; } [data-theme="dark"] .ai-steam .c2 { color: #6F7FD0; } [data-theme="dark"] .ai-steam .c3 { color: #A869BF; } [data-theme="dark"] .ai-steam .c4 { color: #D3609A; }
.ai-steam .d1 { animation-delay: .45s; } .ai-steam .d2 { animation-delay: .9s; } .ai-steam .d3 { animation-delay: 1.35s; } .ai-steam .d4 { animation-delay: 1.8s; } .ai-steam .d5 { animation-delay: 2.2s; }
.ai-pot-h { font: 700 24px var(--font-display); }
.ai-pot-line { min-height: 1.5em; color: var(--muted); font-size: 16px; }
@keyframes cpspin { to { transform: rotate(360deg); } }
@keyframes cpsteam { 0% { transform: translateY(0) scale(.25) rotate(0deg); opacity: 0; } 18% { opacity: 1; transform: translateY(-14px) scale(.9) rotate(30deg); } 55% { transform: translateY(-48px) scale(1) rotate(80deg); opacity: 1; } 100% { transform: translateY(-96px) scale(.35) rotate(140deg); opacity: 0; } }
@keyframes cpsteamL { 0% { transform: translate(0, 0) scale(.25) rotate(0deg); opacity: 0; } 18% { opacity: 1; transform: translate(-4px, -14px) scale(.9) rotate(-30deg); } 55% { transform: translate(-12px, -46px) scale(1) rotate(-80deg); opacity: 1; } 100% { transform: translate(-22px, -90px) scale(.35) rotate(-140deg); opacity: 0; } }
@keyframes cpsteamR { 0% { transform: translate(0, 0) scale(.25) rotate(0deg); opacity: 0; } 18% { opacity: 1; transform: translate(4px, -14px) scale(.9) rotate(30deg); } 55% { transform: translate(12px, -46px) scale(1) rotate(80deg); opacity: 1; } 100% { transform: translate(22px, -90px) scale(.35) rotate(140deg); opacity: 0; } }
@keyframes cpwisp { 0% { transform: translateY(0) scale(.5); opacity: 0; } 20% { opacity: .9; transform: translateY(-12px) scale(.9); } 100% { transform: translateY(-58px) scale(1.15); opacity: 0; } }
@keyframes cpwispL { 0% { transform: translate(0, 0) scale(.5); opacity: 0; } 20% { opacity: .9; transform: translate(-3px, -12px) scale(.9); } 100% { transform: translate(-10px, -56px) scale(1.15); opacity: 0; } }
@keyframes cpwispR { 0% { transform: translate(0, 0) scale(.5); opacity: 0; } 20% { opacity: .9; transform: translate(3px, -12px) scale(.9); } 100% { transform: translate(10px, -56px) scale(1.15); opacity: 0; } }
@keyframes cpbob { 50% { transform: translateY(-2px); } }
/* nothing moves: the first sparkles stay put, the first status line shows */
@media (prefers-reduced-motion: reduce) {
  .ai-spin::before, .ai-pot-disc::before, .ai-pot-pan, .ai-steam .st, .ai-shimmer { animation: none; }
  .ai-steam .st { opacity: 1; }
  .ai-steam .st.d1 { transform: translate(-10px, -30px); } .ai-steam .st.d2 { transform: translate(10px, -50px); } .ai-steam .st.d3, .ai-steam .st.d4, .ai-steam .st.d5 { display: none; }
}

/* Ask Cocamoo (R8-AI-Ask) and the free accounts' door (R8-AI-Free): a sheet */
.ai-scrim { position: fixed; inset: 0; z-index: 80; background: var(--scrim); }
.ai-scrim[hidden] { display: none; }
.ai-sheet {
  position: fixed;
  z-index: 81;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
  max-height: 94vh;
  overflow-y: auto;
  padding: 10px 22px calc(20px + env(safe-area-inset-bottom));
  border-radius: var(--r-5) var(--r-5) 0 0;
  background: var(--ground);
  box-shadow: 0 -20px 50px rgba(0, 0, 0, .18);
}
.ai-sheet[hidden] { display: none; }
@media (min-width: 700px) {
  .ai-sheet { left: 50%; right: auto; bottom: auto; top: 5vh; width: min(560px, 92vw); max-height: 90vh; transform: translateX(-50%); border-radius: var(--r-5); }
  .ai-sheet-grip { display: none; }
}
.ai-sheet-grip { align-self: center; width: 36px; height: 5px; border-radius: 3px; background: var(--ring); }
.ai-sheet-head { display: flex; align-items: center; gap: 14px; }
.ai-sheet-icon { display: grid; flex: 0 0 auto; width: 40px; height: 40px; place-items: center; border-radius: var(--r-3); background: var(--ai); box-shadow: var(--ai-shadow); color: var(--white); }
.ai-sheet-icon svg { width: 20px; height: 20px; }
.ai-sheet-h { flex: 1 1 auto; font: 700 26px/1.15 var(--font-display); }
.ai-sheet-cancel { min-height: 44px; border: 0; background: none; color: var(--plum-text); font: 600 17px var(--font-body); cursor: pointer; }
.ai-ask-text { width: 100%; min-height: 150px; padding: 16px 18px; color: var(--ink); font: 400 18px/1.5 var(--font-body); resize: vertical; }
.ai-ask-text:focus { outline: none; box-shadow: 0 0 0 3px var(--plum-glow); }
.ai-sheet-label { margin-bottom: -4px; color: var(--muted); font-weight: 700; }
.ai-starts { display: flex; flex-wrap: wrap; gap: 8px; }
.ai-start { min-height: 44px; padding: 0 16px; border: 0; border-radius: var(--r-pill); background: var(--card); box-shadow: var(--raise), 0 0 0 1px var(--line); color: var(--ink); font: 600 15px var(--font-body); cursor: pointer; }
.ai-start[aria-pressed="true"] { background: var(--plum-button); box-shadow: var(--plum-shadow); color: var(--white); }
.ai-wells { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.ai-well { position: relative; display: flex; flex-direction: column; gap: 1px; padding: 8px 12px; border-radius: var(--r-4); background: var(--well-bg); box-shadow: var(--well); color: var(--muted); font-size: 13px; }
.ai-well select { width: 100%; border: 0; padding: 0; background: none; color: var(--ink); font: 600 17px var(--font-body); -webkit-appearance: none; appearance: none; cursor: pointer; }
.ai-well select:focus { outline: none; }
.ai-well:focus-within { box-shadow: var(--well), 0 0 0 2px var(--plum-glow); }
.ai-sheet-error { color: var(--plum-text); font-weight: 600; }
/* Nov 12: photos of a recipe under "A recipe, notes or a photo" (ai.js, 5) */
.ai-photos { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.ai-photo { position: relative; width: 64px; height: 64px; border-radius: var(--r-3); overflow: hidden; box-shadow: var(--raise), 0 0 0 1px var(--line); }
.ai-photo img { display: block; width: 100%; height: 100%; object-fit: cover; }
.ai-photo-x { position: absolute; top: 2px; right: 2px; display: grid; place-items: center; width: 24px; height: 24px; padding: 0; border: 0; border-radius: 50%; background: var(--glass); color: var(--ink); cursor: pointer; }
.ai-photo-x::before { content: ""; position: absolute; inset: -10px; } /* a 44px tap area */
.ai-photo-x svg { width: 14px; height: 14px; }
.ai-photo-add { gap: 6px; cursor: pointer; }
.ai-photo-add svg { width: 18px; height: 18px; color: var(--plum-icon); }
.ai-photo-add:focus-within { outline: 2px solid var(--plum-icon); outline-offset: 2px; }
.ai-photo-hint { flex: 1 1 160px; font-size: 13px; color: var(--muted); }
/* Nov 12, later (Dave; ai.js 6): what you can ask for, above the box (a
   guide, not buttons: "Or start from" read as a choice you had to make) */
.ai-kinds { display: grid; gap: 8px; color: var(--muted); font-size: 15px; line-height: 1.4; }
.ai-kinds-h { color: var(--ink); font-weight: 700; }
.ai-kinds ul { display: grid; gap: 4px; margin: 0; padding: 0; list-style: none; }
.ai-kinds li { position: relative; padding-left: 16px; }
.ai-kinds li::before { content: ""; position: absolute; left: 2px; top: .6em; width: 6px; height: 6px; border-radius: 50%; background: var(--ai-ink); }
.ai-kinds li b { color: var(--ink); font-weight: 600; }
.ai-kinds li b::after { content: ": "; font-weight: 400; color: var(--muted); }
.ai-kinds-photo .text-link { min-height: 32px; padding: 0; border: 0; background: none; font: 700 15px var(--font-body); cursor: pointer; }
@media (pointer: coarse) { .ai-kinds-photo .text-link { min-height: 44px; } }
@media (min-width: 700px) { .ai-kinds ul { grid-template-columns: 1fr 1fr; column-gap: 18px; } }
/* From a photo, its own sheet: one big place to add them first, then the
   photos on top (bigger) and "Anything to add?" under them */
.ai-photo-drop {
  position: relative; display: flex; flex: 1 1 100%; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  min-height: 200px; padding: 24px; border-radius: var(--r-5); background: var(--well-bg); box-shadow: var(--well);
  text-align: center; cursor: pointer;
}
.ai-photo-drop svg { width: 34px; height: 34px; color: var(--plum-icon); }
.ai-photo-drop-h { color: var(--plum-text); font: 700 18px var(--font-body); }
.ai-photo-drop-p { color: var(--muted); font-size: 14px; }
.ai-photo-drop:hover .ai-photo-drop-h { text-decoration: underline; }
.ai-photo-drop:focus-within { outline: 2px solid var(--plum-icon); outline-offset: 2px; }
.ai-sheet .ai-photo { width: 96px; height: 120px; }
.ai-sheet .ai-ask-text[rows="3"] { min-height: 96px; }
.ai-write[hidden] { display: none; }
.ai-sheet .ai-photo-hint { flex: 0 1 auto; }
/* Nov 10: the monthly or daily cap: a note, not an error (ai_limit_message()) */
.ai-sheet-error.is-calm { padding: 12px 14px; border-radius: var(--r-4); background: var(--well-bg); color: var(--ink); font-weight: 400; line-height: 1.5; }
.ai-sheet-foot { display: flex; flex-direction: column; gap: 10px; margin-top: 8px; }
/* the sheet's commit: the large pill; full width on phones, its own width from 700px */
.ai-sheet-foot .ai-btn { width: 100%; min-height: var(--btn-lg); font-size: 16px; font-weight: 700; }
@media (min-width: 700px) { .ai-sheet-foot .ai-btn { align-self: center; width: auto; min-width: 220px; padding: 0 28px; } }
.ai-sheet-note { color: var(--muted); font-size: 14px; line-height: 1.45; text-align: center; }
/* Nov 12 (round 10 carry-over, step 3): free accounts see the live loader
   (was a blurred shimmer and a lock), on the AI wash, a little smaller */
.ai-free-pot { display: grid; place-items: center; min-height: 236px; padding: 4px 16px; }
.ai-free-pot .ai-pot { padding: 14px 0; gap: 12px; }
.ai-free-pot .ai-pot-disc { width: 112px; height: 112px; }
.ai-free-pot .ai-pot-pan { width: 64px; height: 64px; margin-top: 22px; } .ai-free-pot .ai-steam { top: 46px; }
.ai-free-pot .ai-pot-h { font-size: 19px; }
.ai-free-pot .ai-pot-line { min-height: 1.5em; font-size: 15px; }
.ai-free-h { font: 700 22px/1.2 var(--font-display); }
.ai-free-p { color: var(--muted); font-size: 16px; line-height: 1.5; }
.ai-free-acts { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.ai-free-acts > * { min-height: 52px; justify-content: center; text-align: center; font-size: 16px; }
@media (max-width: 420px) { .ai-free-acts { grid-template-columns: 1fr; } }

/* ==========================================================================
   ONE MAIN BUTTON PER VIEW (Oct 29, the one button family)
   One filled button (plum, or the AI pill) in sight at a time. The top
   bar never carries a fill: Add a recipe and Join free are white pills
   with a plum icon or word, and Add a recipe steps aside on /add and /write
   where it would only repeat the page.
   ========================================================================== */
.topbar-add svg { color: var(--plum-icon); }
.topbar-join { color: var(--plum-text); }
body.page-add .topbar-add, body.page-write .topbar-add { display: none; }
body.join-in-view .topbar-join { visibility: hidden; } /* a locked recipe's join panel is on screen (7-page.js); visibility, so the bar doesn't shift */

/* ==========================================================================
   ONE SMALL MENU (Nov 10, Cocamoo.menu() in site.js): a glass panel by the
   button that opened it, the site's menu look (grey hover; a chosen row
   grey with bold plum words). Used by Up next's ✕ and the Shopping list's ···.
   ========================================================================== */
.cm-menu {
  position: fixed; z-index: 90; display: flex; flex-direction: column; min-width: 220px; max-width: min(320px, calc(100vw - 16px));
  padding: 6px; border-radius: var(--r-4); background: var(--glass);
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  box-shadow: var(--glass-shadow), var(--shadow-menu); animation: panel-in .16s var(--ease);
}
@media (prefers-reduced-motion: reduce) { .cm-menu { animation: none; } }
.cm-menu-item {
  display: flex; align-items: center; min-height: 44px; padding: 0 14px; border: 0; border-radius: var(--r-3);
  background: none; color: var(--ink); font: 500 16px var(--font-body); text-align: left; cursor: pointer;
}
.cm-menu-item:hover, .cm-menu-item:focus-visible { background: var(--well-bg); outline: none; }
.cm-menu-item.is-on { background: var(--well-bg); color: var(--plum-text); font-weight: 700; }
.cm-menu-item.is-danger { color: var(--error); }
.cm-menu-note { padding: 8px 14px 6px; font-size: 13px; line-height: 1.4; color: var(--muted); }
/* Nov 13 (Dave: give the desktop menus the ☰ sheet's finesse): a row with a
   picture (a gathering's) or a round icon, the name (two lines at most),
   and a quiet second line */
.cm-menu:has(.is-rich) { min-width: 280px; max-width: min(360px, calc(100vw - 16px)); }
.cm-menu-item.is-rich { gap: 12px; min-height: 56px; padding: 6px 12px 6px 8px; }
.cm-menu-thumb { flex: 0 0 auto; width: 52px; height: 40px; overflow: hidden; border-radius: var(--r-2); background: var(--band); }
.cm-menu-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cm-menu-ic { flex: 0 0 auto; width: 36px; height: 36px; margin: 0 8px 0 8px; border-radius: 50%; display: grid; place-items: center; background: var(--well-bg); color: var(--ink); }
.cm-menu-ic svg { width: 18px; height: 18px; }
.cm-menu-words { display: grid; min-width: 0; line-height: 1.25; }
.cm-menu-name { overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
.cm-menu-detail { font-size: 13px; font-weight: 500; color: var(--muted); }
.cm-menu-item.is-on .cm-menu-detail { color: var(--muted); }

/* ==========================================================================
   SWIPE ON A ROW (Nov 10, Cocamoo.swipe() in site.js; like Apple Mail).
   Behind the row, one well runs right across (no gaps, whatever the
   distance); the round buttons sit at its right edge, 36px in their own
   44px-wide columns, centred on the row. The face slides over whole and
   rounds its right corners as it goes. A long swipe stretches the main
   action's circle into a pill that fills the space the face leaves, the
   other buttons fading back, then the row leaves.
   ========================================================================== */
.swipe-row { position: relative; overflow: hidden; }
.swipe-face { position: relative; z-index: 1; background: inherit; touch-action: pan-y; transition: transform .2s var(--ease), border-radius .18s var(--ease), box-shadow .18s var(--ease); }
.swipe-row.is-moving .swipe-face, .swipe-row.is-open .swipe-face { border-radius: 0 var(--r-4) var(--r-4) 0; box-shadow: 4px 0 10px -6px rgba(0, 0, 0, .25); }
.swipe-acts { position: absolute; inset: 0; display: flex; align-items: center; justify-content: flex-end; background: var(--well-bg); }
.swipe-btns { display: flex; align-items: center; height: 100%; padding: 0 6px 0 10px; transition: opacity .15s var(--ease); }
.swipe-act { display: grid; place-items: center; width: 46px; height: 100%; min-height: 44px; padding: 0; border: 0; background: none; color: #FFFFFF; cursor: pointer; }
.swipe-act:not(.has-icon) { width: auto; min-width: 72px; padding: 0 14px; background: var(--swipe-grey); font: 600 15px var(--font-body); }
.swipe-dot { display: grid; place-items: center; width: 36px; height: 36px; border-radius: 50%; background: var(--swipe-grey); color: #FFFFFF; box-shadow: 0 1px 2px rgba(0, 0, 0, .14); }
.swipe-dot svg { width: 17px; height: 17px; }
.swipe-act.is-plum .swipe-dot, .swipe-act.is-plum:not(.has-icon) { background: var(--plum-button, var(--plum)); }
.swipe-act.is-danger .swipe-dot, .swipe-act.is-danger:not(.has-icon) { background: var(--swipe-red); }
.swipe-act:focus-visible { outline: none; }
.swipe-act:focus-visible .swipe-dot { outline: 2px solid var(--plum-icon); outline-offset: 2px; }
/* the long swipe: a pill from the right edge, as wide as the space left (set by site.js) */
.swipe-full {
  position: absolute; top: 50%; right: 10px; z-index: 0; display: flex; align-items: center; justify-content: flex-start;
  width: 36px; height: 36px; padding: 0 9px; border-radius: var(--r-pill); color: #FFFFFF; opacity: 0;
  transform: translateY(-50%); transition: width .16s var(--ease), opacity .12s var(--ease);
}
.swipe-full.is-plum { background: var(--plum-button, var(--plum)); }
.swipe-full.is-danger { background: var(--swipe-red); }
.swipe-full svg { flex: 0 0 auto; width: 18px; height: 18px; }
.swipe-row.is-full .swipe-full { opacity: 1; }
.swipe-row.is-full .swipe-btns { opacity: 0; }
.swipe-row.is-gone .swipe-face { opacity: .4; }
@media (prefers-reduced-motion: reduce) { .swipe-face, .swipe-full, .swipe-btns { transition: none; } }

/* ==========================================================================
   SEASONAL: FALLING LEAVES (Nov 11, brought over from the Webflow site's
   Embed 3/4). leaves.js builds #leaves-container itself, only between the
   dates at the top of that file. Never in the way: no clicks, and off for
   reduced motion. (The cursor's gentle wind is in leaves.js since Nov 12.)
   ========================================================================== */
#leaves-container {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 9999;
  overflow: hidden;
}
/* Nov 12: leaves.js moves each leaf itself (translate, rotate, and a scaleY
   for turning over), so no keyframes here any more. */
.leaf {
  position: absolute;
  top: 0;
  left: 0;
  will-change: transform;
}
.leaf-body {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}
.leaf-body .leaf-vein {
  fill: none;
  stroke: var(--leaf-vein);
  stroke-width: 2.5;
  stroke-linecap: round;
}
@media (prefers-reduced-motion: reduce) { #leaves-container { display: none; } }
@media print { #leaves-container { display: none; } }

/* ==========================================================================
   SWAP SHEETS, SHARED (round 10, 1.2): the recipe page's swap sheet and the
   Shopping list's Out of stock? both open for every ingredient now. With no
   swaps in the shared list, members get "For this recipe" (an AI area) and
   everyone else "No swaps for this one yet" with the members' teaser.
   (The .swap-ai rules moved here from recipe.css.)
   ========================================================================== */
.swap-ai { padding: 14px 16px; }
.swap-ai .ai-label { font-size: 15px; margin-bottom: 6px; }
.swap-ai-text { display: flex; gap: 10px; align-items: flex-start; font-size: 15px; line-height: 1.5; }
.swap-ai-foot { margin-top: 6px; color: var(--muted); font-size: 13px; }
.swap-none { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; padding: 16px; border-radius: var(--r-5); background: var(--card); box-shadow: var(--raise); }
.swap-none-h { font-weight: 700; }
.swap-none-p { color: var(--muted); font-size: 15px; line-height: 1.45; }

/* ==========================================================================
   SCREENSHOT PEEKS (Nov 12, round 10 carry-over step 4; partials/
   shot-peek.php): the top of a real screenshot in the home page's phone
   frame, beside a card's words (below them under 560px), cut off by the
   card's bottom edge and fading out. The card: .has-peek, its words in
   .peek-text. --peek-bleed is the card's bottom padding, so the phone
   runs right to the edge.
   ========================================================================== */
/* doubled class: these cards set their own display in later files */
.has-peek.has-peek { position: relative; display: grid; gap: 22px; overflow: hidden; --peek-bleed: 24px; }
.g-empty.has-peek { --peek-bleed: 22px; }
@media (min-width: 700px) { .lib-pitch.has-peek { --peek-bleed: 30px; } }
.peek-text { display: flex; flex-direction: column; align-items: flex-start; gap: 12px; min-width: 0; }
.peek-text > .btn-block, .peek-text > .lib-pitch-signin { align-self: stretch; }
.peek-text > .lib-ticks { margin: 8px 0 12px; }
.shot-peek { justify-self: center; width: 176px; height: 230px; margin-bottom: calc(var(--peek-bleed) * -1); overflow: hidden; -webkit-mask-image: linear-gradient(#000 62%, transparent); mask-image: linear-gradient(#000 62%, transparent); }
.shot-phone { display: block; width: 100%; padding: 6px; border-radius: 30px; background: var(--dark); box-shadow: 0 16px 32px rgba(0, 0, 0, .14); }
.shot-phone img { width: 100%; height: auto; aspect-ratio: 750 / 1624; border-radius: 25px; background: var(--ground); }
@media (min-width: 560px) {
  .has-peek.has-peek { grid-template-columns: minmax(0, 1fr) 176px; align-items: end; column-gap: 28px; }
  .shot-peek { height: 260px; }
}

/* About, signed out (Nov 12): the story ends on the plan cards */
.about-plans { max-width: 1040px; margin: 48px auto 0; padding: 0 var(--gutter); }
.about-plans-h { margin-bottom: 24px; text-align: center; font: 700 clamp(28px, 4vw, 40px)/1.15 var(--font-display); }

/* ==========================================================================
   HELP & CONTACT (Nov 12; pages/contact.php, app/lib/contact.php)
   ========================================================================== */
.contact-lead { margin-bottom: 28px; }
.contact-h { margin: 0 0 12px; font: 700 22px var(--font-display); }
.contact-faq { display: grid; gap: 10px; margin-bottom: 32px; }
.contact-q { padding: 0; border-radius: var(--r-4); }
.contact-q summary { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 52px; padding: 12px 18px; font-weight: 600; font-size: 17px; cursor: pointer; list-style: none; }
.contact-q summary::-webkit-details-marker { display: none; }
.contact-q summary::after { content: "+"; flex: none; color: var(--plum-icon); font-size: 22px; font-weight: 400; }
.contact-q[open] summary::after { content: "–"; }
.contact-q p { margin: 0; padding: 0 18px 16px; font-size: 16px; line-height: 1.55; color: var(--muted); }
.contact-q a, .contact-alt a, .contact-sent a:not(.btn) { color: var(--plum-text); }
.contact-form-card, .contact-sent { padding: 22px 20px; border-radius: var(--r-5); }
@media (min-width: 700px) { .contact-form-card, .contact-sent { padding: 28px 30px; } }
.contact-sent p { margin: 0 0 14px; font-size: 17px; line-height: 1.55; }
.contact-form { display: grid; gap: 16px; }
.contact-row { display: grid; gap: 16px; }
@media (min-width: 560px) { .contact-row { grid-template-columns: 1fr 1fr; } }
.contact-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.contact-field > span:first-child { font-size: 14px; font-weight: 700; color: var(--muted); }
.contact-field input, .contact-field select, .contact-field textarea {
  width: 100%; min-height: 48px; padding: 10px 14px; border: 0; border-radius: var(--r-4);
  background: var(--well-bg); box-shadow: var(--well); color: var(--ink); font: 400 17px/1.4 var(--font-body);
}
.contact-field textarea { resize: vertical; }
.contact-field textarea::placeholder { color: var(--muted); font-style: italic; }
.contact-field input:focus, .contact-field select:focus, .contact-field textarea:focus { outline: none; box-shadow: var(--well), 0 0 0 2px var(--plum-icon); }
.contact-select { position: relative; display: block; }
.contact-select select { -webkit-appearance: none; appearance: none; padding-right: 40px; cursor: pointer; }
.contact-select::after { content: ""; position: absolute; top: 50%; right: 16px; width: 10px; height: 10px; margin-top: -7px; border-right: 2px solid var(--plum-icon); border-bottom: 2px solid var(--plum-icon); transform: rotate(45deg); pointer-events: none; }
.contact-trap { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.contact-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px; }
.contact-fine { flex: 1 1 200px; margin: 0; font-size: 14px; color: var(--muted); }
.contact-error { margin: 0 0 14px; font-weight: 600; color: var(--error); }
.contact-alt { margin-top: 18px; font-size: 15px; color: var(--muted); text-align: center; }

/* ==========================================================================
   SOLID ICONS (Nov 13, Dave chose solid). The sprite's icons are used with
   <svg><use href="#i-name"/></svg> as before. .ic-inline sits one in a line
   of text (the rating stars). Any icon of the set (public/assets/icons/
   solid/<group>/<name>.svg, listed in icons.json) can also be shown as a
   mask, so it takes the colour of the words around it:
     <span class="ic-solid" style="--ic: url(/assets/icons/solid/produce/apple.svg)"></span>
   (php: solid_icon('produce/apple'); js: Cocamoo.solidIcon('produce/apple'))
   ========================================================================== */
.ic-inline { display: inline-block; width: 1em; height: 1em; vertical-align: -.125em; }
:where(.ic-solid) { display: inline-block; width: 1.25em; height: 1.25em; vertical-align: -.25em; } /* a default size any rule can beat */
.ic-solid {
  flex: none;
  background: currentColor;
  -webkit-mask: var(--ic) center / contain no-repeat; mask: var(--ic) center / contain no-repeat;
}

/* ==========================================================================
   ADD A PHOTO (Nov 14, Dave; partials/photo-ask.php): a quiet raised card
   on a gathering and the Community page for someone with no photo. Their
   initial in the circle everyone sees now, a camera on it; Add a photo
   goes to Profile & billing, Not now hides it (site.js).
   ========================================================================== */
.photo-ask { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 14px; margin: 0 0 18px; padding: 14px 16px; border-radius: var(--r-4); background: var(--card); box-shadow: var(--raise); }
.photo-ask-av { position: relative; display: grid; flex: 0 0 auto; width: 44px; height: 44px; place-items: center; border-radius: 50%; background: var(--band); color: var(--muted); font: 700 18px var(--font-body); }
.photo-ask-cam { position: absolute; right: -4px; bottom: -4px; display: grid; width: 22px; height: 22px; place-items: center; border-radius: 50%; background: var(--card); box-shadow: 0 0 0 2px var(--card); color: var(--plum-icon); }
.photo-ask-cam svg { width: 14px; height: 14px; }
.photo-ask-text { display: grid; flex: 1 1 220px; gap: 2px; min-width: 0; }
.photo-ask-text b { font-size: 16px; }
.photo-ask-text span { font-size: 14px; line-height: 1.4; color: var(--muted); }
.photo-ask-acts { display: flex; align-items: center; gap: 4px; }
.photo-ask-later { position: relative; min-height: 32px; padding: 0 10px; border: 0; background: none; color: var(--muted); font: 600 14px var(--font-body); cursor: pointer; }
.photo-ask-later::after { content: ""; position: absolute; inset: -6px 0; } /* 44px to tap */
.photo-ask-later:hover { color: var(--ink); }
@media (max-width: 699px) { .photo-ask-acts { flex: 1 0 100%; padding-left: 58px; } }
