/* ============================================================
   Grocery Flow — implements the Grocery Flow Design System
   Tokens mirror /design/colors_and_type.css verbatim.
   Single-typeface system: Manosque for displays/headers/wordmark,
   system sans for body/UI.
   ============================================================ */

/* Display face — Manosque, loaded from /fonts/ as the actual headline
   typeface (wordmark, screen titles, sheet titles, empty-state marks).
   Body/UI keep the platform system stack so first paint stays instant
   and offline reliability is preserved. */
@font-face {
  font-family: 'Manosque';
  src: url('./fonts/Manosque-Regular.woff2') format('woff2'),
       url('./fonts/Manosque-Regular.woff')  format('woff');
  font-weight: 400 800;
  font-style: normal;
  font-display: swap;
}

:root {
  /* ---------- COLOR ---------- */
  --paper:        #fdfbf7;
  --paper-2:      #f6f1e7;
  --paper-3:      #efe8d8;
  --ink:          #1a1a1a;
  --ink-soft:     #2c2925;
  --muted:        #6b6259;
  --muted-2:      #978c80;
  --rule:         #ece5d8;
  --rule-strong:  #dcd1bd;

  /* Primary tomato ramp — 50 (lightest tint) → 700 (darkest shade).
     500 is the base brand color. Use the ramp for chips, state layers,
     hover/active/focus, and badges so visual hierarchy stays coherent.
     Generated to be roughly perceptually-uniform in OKLCH lightness. */
  --tomato-50:    #fdeee8;   /* faintest tint — subtle hover wash */
  --tomato-100:   #fad6c9;   /* chip background */
  --tomato-200:   #f3b69e;   /* secondary tint */
  --tomato-300:   #e98863;   /* hover on solid surfaces */
  --tomato-400:   #df6c43;   /* hover on primary buttons */
  --tomato-500:   #d4502a;   /* BASE — brand */
  --tomato-600:   #b94320;   /* active / pressed primary */
  --tomato-700:   #93331a;   /* darkest — outline-text on light */
  --tomato:       var(--tomato-500);
  --tomato-soft:  var(--tomato-100);
  --tomato-deep:  #a93d1f;   /* legacy — kept for compat with existing rules */
  --olive:        #6b8e4e;
  --olive-soft:   #dfe7d3;
  --olive-deep:   #506e39;
  --berry:        #c63d6b;
  --berry-soft:   #f3d3df;
  --berry-deep:   #8e2c50;

  /* category accents */
  --cat-produce:   #6b8e4e;
  --cat-bakery:    #c97e3a;
  --cat-dairy:     #d9c074;
  --cat-meat:      #b14a3d;
  --cat-frozen:    #6a8fa6;
  --cat-drinks:    #5a7d8c;
  --cat-pantry:    #a07a4a;
  --cat-snacks:    #c63d6b;
  --cat-household: #7a6a8a;

  /* user palette (for share dots) */
  --user-1: #d4502a;
  --user-2: #6b8e4e;
  --user-3: #6a8fa6;
  --user-4: #c63d6b;
  --user-5: #b15a8a;
  --user-6: #4a6b8a;

  /* Elevated-surface token: inputs, cards, emoji discs, drag state.
     In light mode = pure white (pops above paper); in dark mode = a step
     brighter than paper so cards/inputs still feel raised. */
  --surface:    #ffffff;
  --topbar-bg:  rgba(253, 251, 247, 0.92);

  /* ---------- TYPE ---------- */
  /* Single system-font system; --font-display kept as a separate token in
     case a custom display face is reintroduced later. */
  --font-sans:    -apple-system, BlinkMacSystemFont, "SF Pro Text", "Inter", "Segoe UI", Roboto, system-ui, sans-serif;
  --font-display: 'Manosque', "Iowan Old Style", "Charter", "Hoefler Text", "Georgia", "Cambria", "Times New Roman", serif;
  --font-serif:   var(--font-display);   /* alias for compatibility */
  --font-mono:    ui-monospace, "SFMono-Regular", Menlo, monospace;

  --text-xs:   12px;
  --text-sm:   13px;
  --text-base: 15px;
  --text-md:   16px;
  --text-lg:   18px;
  --text-xl:   22px;
  --text-2xl:  28px;
  --text-3xl:  36px;
  --text-4xl:  48px;

  --lh-tight:  1.15;
  --lh-snug:   1.3;
  --lh-normal: 1.5;
  --lh-loose:  1.65;

  --track-tight:  -0.02em;
  --track-normal: 0;
  --track-wide:   0.06em;
  --track-cap:    0.12em;

  --w-regular: 400;
  --w-medium:  500;
  --w-semi:    600;
  --w-bold:    700;

  /* ---------- SPACE / TAP ---------- */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-7: 32px;
  --space-8: 40px;
  --space-9: 56px;
  --space-10: 72px;
  --tap: 44px;

  /* ---------- RADII ----------
     Numbered tokens stay symmetric for spec adherence. The "sticker"
     tokens are deliberately uneven, four-corner radii — used on hero
     surfaces (sheets, list card, saved tiles, primary chips) so the
     UI reads less like a frame in Figma and more like a drawn shape. */
  --r-1: 4px;
  --r-2: 8px;
  --r-3: 12px;
  --r-4: 16px;
  --r-5: 20px;
  --r-pill: 999px;
  --r-default: var(--r-3);
  --r-sticker-sm: 11px 14px 12px 13px;
  --r-sticker-md: 16px 20px 15px 18px;
  --r-sticker-lg: 20px 26px 22px 24px;
  --r-pill-soft:  22px / 28px;

  /* ---------- SHADOWS ---------- */
  --shadow-0: none;
  --shadow-1: 0 1px 0 0 rgba(26, 26, 26, 0.04);
  --shadow-2: 0 2px 6px -2px rgba(26, 26, 26, 0.08);
  --shadow-sheet: 0 -8px 32px -8px rgba(26, 26, 26, 0.18),
                  0 -1px 0 0 var(--rule);
  --shadow-focus: 0 0 0 3px rgba(212, 80, 42, 0.22);
  --shadow-default: var(--shadow-2);

  /* ---------- MOTION ---------- */
  --ease-out:    cubic-bezier(0.2, 0.7, 0.2, 1);
  --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
  --dur-quick: 120ms;
  --dur-base:  180ms;
  --dur-sheet: 220ms;

  /* ---------- LAYOUT ---------- */
  --topbar-h: 56px;
  --tabbar-h: 64px;
  --max-w:    540px;

  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-top:    env(safe-area-inset-top, 0px);
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-quick: 0ms;
    --dur-base:  0ms;
    --dur-sheet: 0ms;
  }
}

/* ---------- DARK MODE
   Auto-applied via prefers-color-scheme; user can pin via [data-theme]. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper:        #0d0c0a;
    --paper-2:      #1f1c18;
    --paper-3:      #2d2922;
    --ink:          #fbf6ec;
    --ink-soft:     #ebe3d2;
    --muted:        #c8beac;
    --muted-2:      #968c7e;
    --rule:         #443d33;
    --rule-strong:  #635947;

    /* Dark-mode tomato ramp — invert the tints so 50 = darkest tint
       on the dark paper, 700 = brightest. Same semantic roles. */
    --tomato-50:    #2a1410;
    --tomato-100:   #4a2418;
    --tomato-200:   #6a3221;
    --tomato-300:   #934120;
    --tomato-400:   #c44a26;
    --tomato-500:   #d4502a;
    --tomato-600:   #e96a44;
    --tomato-700:   #f3997c;
    --tomato-soft:  var(--tomato-100);
    --olive-soft:   #233220;
    --berry-soft:   #4a1f30;

    --surface:      #2a2620;
    --topbar-bg:    rgba(13, 12, 10, 0.94);

    --shadow-1: 0 1px 0 0 rgba(0, 0, 0, 0.55);
    --shadow-2: 0 4px 14px -2px rgba(0, 0, 0, 0.7);
    --shadow-sheet: 0 -10px 40px -8px rgba(0, 0, 0, 0.85),
                    0 -1px 0 0 var(--rule);
    --shadow-focus: 0 0 0 3px rgba(212, 80, 42, 0.55);
  }
}
:root[data-theme="dark"] {
  --paper:        #0d0c0a;
  --paper-2:      #1f1c18;
  --paper-3:      #2d2922;
  --ink:          #fbf6ec;
  --ink-soft:     #ebe3d2;
  --muted:        #c8beac;
  --muted-2:      #968c7e;
  --rule:         #443d33;
  --rule-strong:  #635947;
  --tomato-50:    #2a1410;
  --tomato-100:   #4a2418;
  --tomato-200:   #6a3221;
  --tomato-300:   #934120;
  --tomato-400:   #c44a26;
  --tomato-500:   #d4502a;
  --tomato-600:   #e96a44;
  --tomato-700:   #f3997c;
  --tomato-soft:  var(--tomato-100);
  --olive-soft:   #233220;
  --berry-soft:   #4a1f30;
  --surface:      #2a2620;
  --topbar-bg:    rgba(13, 12, 10, 0.94);
  --shadow-1: 0 1px 0 0 rgba(0, 0, 0, 0.55);
  --shadow-2: 0 4px 14px -2px rgba(0, 0, 0, 0.7);
  --shadow-sheet: 0 -10px 40px -8px rgba(0, 0, 0, 0.85), 0 -1px 0 0 var(--rule);
  --shadow-focus: 0 0 0 3px rgba(212, 80, 42, 0.55);
}

/* Dark-mode polish that needs more than a token swap: lift the sheet
   surface a hair so it reads as a layer above the page, and brighten
   the inactive segmented-control text so the active pill stands out
   without making the inactives feel disabled. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .sheet-content {
    background: linear-gradient(to bottom, #161310 0%, var(--paper) 56px);
  }
  :root:not([data-theme="light"]) .toggle-segmented button { color: var(--ink-soft); }
  :root:not([data-theme="light"]) .toggle-segmented button.is-active { color: var(--ink); }
  :root:not([data-theme="light"]) .add-input,
  :root:not([data-theme="light"]) .field input,
  :root:not([data-theme="light"]) .field textarea {
    background: var(--surface);
  }
}
:root[data-theme="dark"] .sheet-content {
  background: linear-gradient(to bottom, #161310 0%, var(--paper) 56px);
}
:root[data-theme="dark"] .toggle-segmented button { color: var(--ink-soft); }
:root[data-theme="dark"] .toggle-segmented button.is-active { color: var(--ink); }
:root[data-theme="dark"] .add-input,
:root[data-theme="dark"] .field input,
:root[data-theme="dark"] .field textarea {
  background: var(--surface);
}

/* ---------- DENSITY: compact mode (tighter rows, smaller list type) */
:root {
  --row-pad-y: 8px;
  --row-min-h: 44px;
  --row-name-size: 19px;
  --cat-header-pad-y: 10px;
  --cat-header-name-size: 18px;
}
:root[data-density="compact"] {
  --row-pad-y: 1px;
  --row-min-h: 32px;
  --row-name-size: 15px;
  --cat-header-pad-y: 4px;
  --cat-header-name-size: 14px;
}
:root[data-density="compact"] .item-row { gap: 10px; }
:root[data-density="compact"] .item-check { width: 20px; height: 20px; }
:root[data-density="compact"] .item-qty { font-size: 13px; width: 46px; }
:root[data-density="compact"] .cat-header-emoji { width: 22px; height: 22px; font-size: 13px; }
:root[data-density="compact"] .item-edit-btn { width: 28px; height: 28px; min-width: 32px; min-height: 32px; }
:root[data-density="compact"] .item-note { font-size: 11px; }
:root[data-density="compact"] .cat-header-spacer { width: 10px; }
:root[data-density="compact"] .cat-header-qtypad { width: 46px; }

/* =============== Reset =============== */
*, *::before, *::after { box-sizing: border-box; }
html, body {
  margin: 0;
  padding: 0;
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--text-md);
  line-height: var(--lh-normal);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

/* The shader canvas paints the page background. The html element still
   carries the paper colour so the page is never blank: it's the fallback
   you see if WebGL fails to compile, and it's also what shows through
   while the canvas fades in.

   A tiled SVG noise layer is composited on top of the paper colour so
   surfaces feel like printed stock rather than a flat raster. The noise
   is desaturated and held at ~35% alpha; in light mode it reads as ink
   speckle, in dark mode as silver grain — the same texture works for
   both because individual pixels span the full luminance range. The
   shader fades in over both so the moiré never dominates. */
html {
  background:
    url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='240'><filter id='n'><feTurbulence baseFrequency='0.85' numOctaves='2' seed='4' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='240' height='240' filter='url(%23n)' opacity='0.32'/></svg>") repeat,
    var(--paper);
  background-size: 240px 240px, auto;
}
body { background: transparent; }

/* =============== Ambient shader background ================
   A slow WebGL gradient sits behind everything else. Lower opacity keeps
   it lowkey — the html paper colour shows through, so the shader reads
   as a soft mood layer rather than the dominant background. Pointer
   events disabled. Disabled entirely when the user opts out in Settings. */
#bg-canvas {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
  pointer-events: none;
  opacity: 0;
  transition: opacity 600ms var(--ease-out);
}
#bg-canvas.is-ready { opacity: 0.55; }
:root[data-background="off"] #bg-canvas { opacity: 0 !important; }
@media (prefers-reduced-motion: reduce) {
  #bg-canvas { transition: none; }
}
html {
  height: 100%;
  overflow-x: hidden;
  overscroll-behavior-x: none;
}
body {
  min-height: 100vh;
  min-height: 100dvh;
  overflow-x: hidden;
  overscroll-behavior-y: none;
  overscroll-behavior-x: none;
  padding-bottom: calc(var(--tabbar-h) + var(--safe-bottom));
  padding-top: calc(var(--topbar-h) + var(--safe-top));
}
button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
input, textarea {
  font: inherit;
  color: inherit;
  -webkit-appearance: none;
  appearance: none;
}
h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: var(--w-bold);
  letter-spacing: var(--track-tight);
  /* Manosque ships single-weight; let the UA fake bold from the regular
     glyphs so a 700/600 in design specs still reads as emphatic without
     us bundling extra font files. */
  font-synthesis-weight: auto;
}
p { margin: 0; }
small { font-size: 0.82em; color: var(--muted); font-weight: var(--w-regular); }
code {
  font-family: var(--font-mono);
  font-size: 0.86em;
}
::selection { background: var(--tomato); color: #fff; }

/* Visible focus rings — only when navigating with keyboard, never on
   touch/mouse press. Uniform tomato ring across every interactive
   surface so the brand color carries the affordance. */
:where(button, input, textarea, [role="button"], [tabindex]):focus { outline: none; }
:where(button, input, textarea, [role="button"], [tabindex]):focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
  border-radius: var(--r-2);
}
.add-input:focus-visible,
.field input:focus-visible,
.field textarea:focus-visible {
  border-color: var(--tomato);
  box-shadow: var(--shadow-focus);
}
.tab:focus-visible { box-shadow: inset 0 0 0 2px var(--tomato); border-radius: 0; }
.item-row:focus-visible,
.cat-row:focus-visible,
.saved-row:focus-visible,
.sheet-row:focus-visible {
  outline: 2px solid var(--tomato);
  outline-offset: -2px;
  box-shadow: none;
}

/* =============== Per-category tokens (drives category styling)
   --cat: base color (used for chip dots and tinted strips) */
[data-category="produce"]   { --cat: var(--cat-produce); }
[data-category="bakery"]    { --cat: var(--cat-bakery); }
[data-category="dairy"]     { --cat: var(--cat-dairy); }
[data-category="meat"]      { --cat: var(--cat-meat); }
[data-category="frozen"]    { --cat: var(--cat-frozen); }
[data-category="drinks"]    { --cat: var(--cat-drinks); }
[data-category="pantry"]    { --cat: var(--cat-pantry); }
[data-category="snacks"]    { --cat: var(--cat-snacks); }
[data-category="household"] { --cat: var(--cat-household); }
/* fallback for custom categories — JS sets --cat inline */

/* =============== Topbar =============== */
/* The topbar is intentionally chromeless — no background, no blur,
   no border. The wordmark "levitates" above the content. When the
   user scrolls past the LIFT_AT_Y threshold (wired in JS as
   .topbar-lifted on body), the wordmark fades up and out of view,
   making the content area feel uncluttered while shopping. */
.topbar {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: calc(var(--topbar-h) + var(--safe-top));
  padding: var(--safe-top) 14px 0;
  background: transparent;
  border-bottom: none;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 50;
  /* Don't intercept taps when faded — but stay tappable when visible. */
  pointer-events: none;
}
.wordmark {
  position: relative;
  font-family: var(--font-display);
  font-weight: var(--w-semi);
  font-size: var(--text-lg);
  letter-spacing: var(--track-tight);
  color: var(--ink);
  text-align: center;
  white-space: nowrap;
  display: inline-block;
  /* Soft drop-shadow gives the "floating above the page" feel. */
  text-shadow: 0 1px 0 color-mix(in oklch, var(--paper) 60%, transparent),
               0 6px 14px rgba(0, 0, 0, 0.08);
  transition: opacity 280ms var(--ease-out),
              transform 280ms var(--ease-out),
              filter 280ms var(--ease-out);
  pointer-events: auto;
}
.topbar-lifted .wordmark {
  opacity: 0;
  transform: translateY(-12px) scale(0.96);
  filter: blur(1px);
  pointer-events: none;
}
.wordmark-dot {
  position: absolute;
  width: 6px;
  height: 5px;
  background: var(--tomato);
  /* Irregular four-corner radius gives the dot the silhouette of a wet
     ink blot rather than a stamped pixel circle — most-noticed pixel
     in the wordmark, smallest budget for the change. */
  border-radius: 56% 44% 60% 40% / 48% 52% 46% 54%;
  top: 4px;
  right: -8px;
  transform: rotate(-10deg);
  /* Soft halo simulates the bleed of ink into paper. */
  box-shadow: 0 0 0 1px color-mix(in oklch, var(--tomato) 32%, transparent);
}
/* =============== Screens / layout =============== */
.screens {
  max-width: var(--max-w);
  margin: 0 auto;
  padding: 0;
}
.screen { padding-top: var(--space-2); padding-bottom: var(--space-6); }
.screen[hidden] { display: none; }

.eyebrow {
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: var(--w-semi);
  letter-spacing: var(--track-cap);
  text-transform: uppercase;
  color: var(--muted);
}
.hint {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--muted);
  line-height: var(--lh-normal);
  margin: var(--space-2) var(--space-5) var(--space-4);
}

/* =============== Add input =============== */
.add-wrap {
  position: relative;
  margin: var(--space-4) var(--space-4) var(--space-2);
}
.add-form {
  position: relative;
  margin: 0;
  display: flex;
  align-items: stretch;
  gap: 4px;
}
.add-input {
  flex: 1;
  min-width: 0;
  height: 48px;
  padding: 0 14px;
  border: 1.5px solid var(--rule);
  /* Slightly uneven corners — the input feels like a slot drawn on
     paper rather than a perfectly-rounded chip. */
  border-radius: 10px 12px 11px 13px;
  background: var(--surface);
  color: var(--ink);
  font-family: var(--font-sans);
  font-weight: var(--w-regular);
  font-size: var(--text-md);
  transition: border-color var(--dur-quick) var(--ease-out),
              box-shadow var(--dur-quick) var(--ease-out);
}
.add-input::placeholder { color: var(--muted-2); }
.add-input:focus {
  outline: none;
  border-color: var(--tomato);
  box-shadow: var(--shadow-focus);
}
/* Auxiliary input methods (voice, barcode). Each is only present in DOM
   when the underlying browser API is available — feature detected at
   boot — so the form stays uncluttered on devices that can't use them. */
.add-aux-btn {
  flex: 0 0 auto;
  width: 44px;
  height: 48px;
  background: var(--paper-2);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background var(--dur-quick) var(--ease-out),
              color var(--dur-quick) var(--ease-out),
              border-color var(--dur-quick) var(--ease-out);
}
.add-aux-btn:active { background: var(--paper-3); color: var(--ink); }
.add-aux-btn[hidden] { display: none; }
.add-aux-btn.is-listening {
  color: var(--tomato);
  border-color: var(--tomato);
  background: color-mix(in oklch, var(--tomato) 12%, var(--paper));
  animation: gf-mic-pulse 1.3s ease-in-out infinite;
}
@keyframes gf-mic-pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in oklch, var(--tomato) 40%, transparent); }
  50%      { box-shadow: 0 0 0 6px color-mix(in oklch, var(--tomato) 0%,  transparent); }
}
.add-btn {
  flex: 0 0 auto;
  width: 48px;
  height: 48px;
  background: var(--tomato);
  color: #fff;
  /* Corners mirror the input on the opposite diagonal so the pair
     reads as a hand-cut diptych rather than two stamped chips. */
  border-radius: 13px 11px 12px 10px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background var(--dur-quick) var(--ease-out);
  /* Inset highlight + drop-shadow simulate ink-coated metal. */
  box-shadow:
    inset 0 1px 0 color-mix(in oklch, #fff 25%, transparent),
    0 2px 0 var(--tomato-deep);
}
.add-btn:active { background: var(--tomato-deep); }

/* =============== Autocomplete dropdown =============== */
.autocomplete-list {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(100% + 6px);
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--r-3);
  box-shadow: var(--shadow-2);
  list-style: none;
  margin: 0;
  padding: 4px;
  z-index: 30;
  max-height: 320px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  animation: gf-fade var(--dur-base) var(--ease-out);
}
.autocomplete-list[hidden] { display: none; }
.ac-item {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: var(--tap);
  padding: 8px 12px;
  border-radius: var(--r-2);
  cursor: pointer;
  transition: background var(--dur-quick) var(--ease-out);
}
.ac-item:hover,
.ac-item.is-active {
  background: var(--paper-2);
}
.ac-emoji {
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
  line-height: 1;
  flex-shrink: 0;
}
.ac-name {
  flex: 1;
  min-width: 0;
  font-family: var(--font-sans);
  font-weight: var(--w-medium);
  font-size: var(--text-md);
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ac-meta {
  font-family: var(--font-mono);
  font-feature-settings: "tnum" 1;
  font-weight: var(--w-semi);
  font-size: var(--text-sm);
  color: var(--muted);
  flex-shrink: 0;
}
/* "3d ago" / "2wk ago" hint on suggestions that the user has bought
   before — pulled from state.recentItems.lastAdded. Pref-gated via
   #pref-history-checkbox so users who find it noisy can hide it. */
.ac-time {
  font-family: var(--font-sans);
  font-weight: var(--w-medium);
  font-size: var(--text-xs);
  color: var(--muted-2);
  flex-shrink: 0;
  padding: 2px 7px;
  border-radius: var(--r-pill);
  background: var(--paper-2);
  letter-spacing: 0.01em;
}
[data-pref-history="off"] .ac-time { display: none; }

/* =============== List toolbar =============== */
/* Grid layout keeps the 4 text actions evenly sized in a single row so
   long translations ("Erledigte löschen") don't break the visual rhythm.
   The trash button sits on its own track at the end. On the narrowest
   phones the grid wraps to two rows automatically via auto-fit. */
.list-toolbar {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr)) auto;
  align-items: stretch;
  gap: 6px;
  padding: var(--space-3) var(--space-4) var(--space-2);
}
.link-btn {
  height: 38px;
  padding: 0 8px;
  border-radius: var(--r-2);
  background: transparent;
  border: 1px solid var(--rule);
  color: var(--ink);
  font-family: var(--font-sans);
  font-weight: var(--w-medium);
  font-size: var(--text-xs);
  letter-spacing: 0.005em;
  min-height: var(--tap);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  min-width: 0;
  transition: background var(--dur-quick) var(--ease-out),
              border-color var(--dur-quick) var(--ease-out);
}
.link-btn > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.link-btn > svg {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  color: var(--muted);
}
.link-btn:active { background: var(--paper-2); }
.link-btn.danger { color: var(--tomato-deep); }
.link-btn.danger > svg { color: var(--tomato-deep); }
.link-btn.danger:active {
  background: color-mix(in oklch, var(--tomato) 12%, var(--paper));
}
/* Icon-only variant — square, sized to match the row. Placed at the end
   of the grid so the destructive action sits apart from the additive
   ones (import/save/share). */
.link-btn-icon {
  width: 42px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-color: color-mix(in oklch, var(--tomato) 35%, var(--rule));
}
.link-btn-icon > svg { width: 18px; height: 18px; color: var(--tomato-deep); }

/* Below ~420px the 5-column grid gets too tight — fold the four text
   actions to a 2×2 grid with the trash button standing alone to the
   right of the first row. */
@media (max-width: 420px) {
  .list-toolbar {
    grid-template-columns: 1fr 1fr auto;
    grid-template-areas:
      "a b trash"
      "c d trash";
  }
  .list-toolbar > .link-btn:nth-child(1) { grid-area: a; }
  .list-toolbar > .link-btn:nth-child(2) { grid-area: b; }
  .list-toolbar > .link-btn:nth-child(3) { grid-area: c; }
  .list-toolbar > .link-btn:nth-child(4) { grid-area: d; }
  .list-toolbar > .link-btn-icon { grid-area: trash; align-self: stretch; height: auto; }
}

/* Share-status icon button: single person = not shared, two people = shared.
   Replaces the old red shared-banner with a quieter inline indicator. */
.share-status {
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--muted);
  border: 0;
  cursor: pointer;
  transition: background var(--dur-quick) var(--ease-out),
              color var(--dur-quick) var(--ease-out);
}
.share-status svg { width: 20px; height: 20px; }
.share-status:active { background: var(--paper-2); }
.share-status.is-shared {
  color: var(--olive-deep);
  background: var(--olive-soft);
}

/* =============== List =============== */
/* The entire list is one continuous "frosted card" — semi-transparent
   paper background with a saturating backdrop-blur. Aisle blocks inside
   are stacked tiles separated by their own tinted headers, so there are
   no gaps showing the shader through the middle of the list. The shader
   only breathes around the card's outer edges. overflow: clip (not
   hidden) preserves position: sticky on the cat-header inside. */
.items-list {
  margin: 12px 12px 0;
  padding: 0;
  background: color-mix(in oklch, var(--paper) 86%, transparent);
  backdrop-filter: blur(22px) saturate(150%);
  -webkit-backdrop-filter: blur(22px) saturate(150%);
  /* Asymmetric four-corner radius — the whole list reads as one drawn
     tile rather than a CAD-precise rectangle. Each corner deviates by
     2-4px from a clean 18px to stay legibly card-shaped at thumbnail
     size while still feeling hand-cut at hero size. */
  border-radius: var(--r-sticker-md);
  border: 1.5px solid color-mix(in oklch, var(--rule-strong) 55%, transparent);
  /* Two-tone shadow: a soft ambient one and a slightly offset "drawn"
     one, so the card looks placed on the page rather than rendered. */
  box-shadow:
    0 1px 0 color-mix(in oklch, var(--ink) 6%, transparent),
    0 6px 18px -10px rgba(0, 0, 0, 0.18),
    -1px 2px 0 -1px color-mix(in oklch, var(--ink) 5%, transparent);
  overflow: clip;
}
@supports not (backdrop-filter: blur(1px)) {
  .items-list { background: var(--paper); }
}
.items-list:empty { display: none; }

.cat-block {
  /* Stronger mixes than before so each aisle's tint is unmistakable
     against the frosted list card. cat-bg is the header fill, cat-ring
     gives the emoji halo + dividers, cat-fg is the title/count text. */
  --cat-bg:   color-mix(in oklch, var(--cat) 35%, var(--paper));
  --cat-ring: color-mix(in oklch, var(--cat) 60%, var(--paper));
  --cat-fg:   color-mix(in oklch, var(--cat) 78%, var(--ink));
  --cat-accent: color-mix(in oklch, var(--cat) 85%, var(--ink));
  margin: 0;
  background: transparent;
}

.cat-header {
  display: flex;
  align-items: center;
  padding: calc(var(--cat-header-pad-y) + 4px) 20px calc(var(--cat-header-pad-y) + 2px);
  background: var(--cat-bg);
  border-bottom: 2px solid var(--cat-ring);
  position: sticky;
  top: calc(var(--topbar-h) + var(--safe-top) - 1px);
  z-index: 2;
  /* Left edge accent: a 4px coloured stripe in the strong aisle hue.
     Pops against the lighter header fill and reads as a chapter mark
     when scanning the list. inset shadow keeps it inside the rounded
     corner clipping. */
  box-shadow: inset 4px 0 0 var(--cat-accent);
}
/* Stronger divider between adjacent aisles inside the unified card —
   matches the header's bottom border weight so the boundary is clear. */
.cat-block + .cat-block .cat-header {
  border-top: 1.5px solid color-mix(in oklch, var(--cat-ring) 85%, transparent);
}
.cat-header-emoji {
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--surface);
  /* Outer ring in the strong accent + a tighter inner ring in the
     softer cat-ring colour give the emoji puck a "badge" feel that
     reads even when the user is scanning quickly down the list. */
  box-shadow: 0 0 0 1px var(--cat-ring), 0 0 0 2.5px var(--cat-accent);
  flex-shrink: 0;
  font-size: 18px;
  line-height: 1;
}
.cat-header-spacer { width: 14px; flex-shrink: 0; }
.cat-header-qtypad { width: 54px; flex-shrink: 0; }
.cat-header-name {
  /* Absolutely centered in the header so the emoji on the left and
     count on the right don't push the title off-centre. pointer-events:
     none lets any clicks pass through to whatever's behind (no header
     actions today, but defensive). max-width leaves clearance for the
     emoji puck and the count text. */
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
  max-width: calc(100% - 200px);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-family: var(--font-display);
  font-weight: var(--w-bold);
  font-size: calc(var(--cat-header-name-size) * 1.1);
  /* --ink resolves to near-black in light mode and near-white in dark
     mode, so the title stays high-contrast against the tinted bg
     without picking up the category hue itself. */
  color: var(--ink);
  letter-spacing: -0.01em;
  text-transform: capitalize;
  line-height: 1.1;
  text-align: center;
}
/* Now that the name is absolutely positioned, it no longer takes up
   flex space — push the count to the right edge so the row's right
   side doesn't collapse into a left-aligned cluster. */
.cat-header-count { margin-left: auto; }
.cat-header-count {
  font-family: var(--font-sans);
  font-weight: var(--w-semi);
  font-size: var(--text-xs);
  letter-spacing: var(--track-cap);
  text-transform: uppercase;
  color: var(--ink);
  opacity: 0.7;
  font-variant-numeric: tabular-nums;
}

.item-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: var(--row-pad-y) 20px;
  border-bottom: 1px solid color-mix(in oklch, var(--rule) 70%, transparent);
  min-height: var(--row-min-h);
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  transition: background var(--dur-quick) var(--ease-out),
              transform 220ms var(--ease-out);
  position: relative;
  /* Transparent so the cat-block's frosted glass shows through. The
     :active state adds a subtle tint instead of swapping the bg colour. */
  background: transparent;
  z-index: 1;
  /* Reserve vertical panning for the browser; horizontal is ours.
     Without this, the browser claims any touch movement for native scroll
     and fires pointercancel, killing swipe-to-delete on phones. */
  touch-action: pan-y;
}
.item-row:active { background: color-mix(in oklch, var(--ink) 6%, transparent); }
.item-row:last-child { border-bottom: 0; }
@supports not (backdrop-filter: blur(1px)) {
  .item-row { background: var(--paper); }
}

.item-check {
  width: 24px;
  height: 24px;
  border-radius: 6px;
  border: 1.5px solid var(--rule-strong);
  background: var(--surface);
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: transparent;
  transition: background var(--dur-quick) var(--ease-out),
              border-color var(--dur-quick) var(--ease-out),
              color var(--dur-quick) var(--ease-out);
}
.item-row.is-checked .item-check {
  background: var(--olive);
  border-color: var(--olive);
  color: #fff;
}
.item-check svg {
  width: 16px;
  height: 16px;
}

.item-qty {
  font-family: var(--font-mono);
  font-feature-settings: "tnum" 1;
  font-weight: var(--w-semi);
  font-size: var(--text-md);
  color: var(--muted);
  width: 54px;
  flex-shrink: 0;
}
.item-name {
  flex: 1;
  min-width: 0;
  font-family: var(--font-sans);
  font-weight: var(--w-medium);
  font-size: var(--row-name-size);
  color: var(--ink);
  letter-spacing: -0.005em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: color var(--dur-quick) var(--ease-out);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.item-name-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.item-note {
  font-size: 13px;
  font-weight: var(--w-regular);
  color: var(--muted);
  letter-spacing: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.item-row.is-checked .item-name {
  color: var(--muted);
  text-decoration: line-through;
  font-weight: var(--w-regular);
}
.item-author-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06);
}
.item-edit-btn {
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  border-radius: 50%;
  flex-shrink: 0;
  margin: 0 -4px 0 -4px;
  min-width: var(--tap);
  min-height: var(--tap);
  background: transparent;
}
.item-edit-btn:active { background: var(--paper-2); color: var(--ink); }
.item-edit-btn svg { width: 20px; height: 20px; }

/* Empty state */
.empty-state {
  text-align: center;
  padding: 64px 24px 24px;
}
.empty-mark {
  position: relative;
  display: inline-block;
  font-family: var(--font-display);
  font-weight: var(--w-bold);
  font-size: 84px;
  line-height: 1;
  color: var(--ink);
  margin-bottom: var(--space-3);
  letter-spacing: -0.04em;
  /* Slight stamp rotation + soft drop-shadow so the glyph reads as
     pressed-in ink rather than a perfectly rendered character. */
  transform: rotate(-2.4deg);
  filter: drop-shadow(0 1px 0 color-mix(in oklch, var(--ink) 10%, transparent));
}
.empty-mark .wordmark-dot {
  width: 14px;
  height: 14px;
  top: 16px;
  right: -8px;
}
.empty-state h2 {
  font-family: var(--font-display);
  font-weight: var(--w-bold);
  font-size: 24px;
  letter-spacing: var(--track-tight);
  color: var(--ink);
  margin-bottom: var(--space-2);
}
.empty-state p {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--muted);
  line-height: var(--lh-normal);
}

/* =============== Store screen =============== */
.store-header {
  padding: 20px 20px 4px;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
}
.store-title {
  font-family: var(--font-display);
  font-weight: var(--w-bold);
  font-size: 28px;
  color: var(--ink);
  letter-spacing: var(--track-tight);
}
.store-header-actions {
  display: flex;
  gap: 4px;
}

.categories-list { padding: var(--space-2) 0; }

/* Walker overlay on the Stores subtab — a faint dashed line down the
   left edge with a person stacked above a cart that steps from one
   aisle to the next on a loop. Visual cue that "this list IS your
   walking route". The actual position is set by JS (animateStoreWalker)
   so the walker lands exactly on each cat-row's vertical center. */
.store-walker-wrap {
  position: relative;
}
.store-walker-wrap.is-active .categories-list {
  /* Make room for the walker rail on the left so cat-rows don't
     overlap the dashed track. */
  padding-left: 42px;
}
.store-walker-track {
  position: absolute;
  left: 20px;
  top: 12px;
  bottom: 12px;
  width: 2px;
  border-left: 2px dashed color-mix(in oklch, var(--tomato-500) 45%, var(--rule));
  pointer-events: none;
  opacity: 0;
  transition: opacity 320ms var(--ease-out);
}
.store-walker-wrap.is-active .store-walker-track { opacity: 1; }
.store-walker {
  position: absolute;
  left: -12px;          /* centered on the dashed track (track is at left:20px, walker is 26px wide) */
  top: 0;
  width: 26px;
  display: none;        /* hidden unless wrap.is-active turns it on */
  flex-direction: column;   /* person stacked above cart */
  align-items: center;
  gap: 0;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.16));
  /* Smooth jumps between cat-rows. JS sets `top`; CSS eases between
     each setting so the walker glides rather than teleports. */
  transition: top 700ms cubic-bezier(.4, 0, .2, 1);
  will-change: top;
  pointer-events: none;
}
.store-walker-wrap.is-active .store-walker { display: flex; }
.store-walker-person {
  font-size: 18px;
  line-height: 1;
  animation: store-walker-bob 0.7s ease-in-out infinite;
  display: inline-block;
}
.store-walker-cart {
  font-size: 16px;
  line-height: 1;
  margin-top: -2px;
}
@keyframes store-walker-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-2px); }
}
@media (prefers-reduced-motion: reduce) {
  .store-walker { transition: none; opacity: 1; }
  .store-walker-person { animation: none; }
}
.cat-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 20px;
  border-bottom: 1px solid var(--rule);
  background: var(--paper);
  touch-action: pan-y;
  position: relative;
  transition: box-shadow var(--dur-base) var(--ease-out),
              background var(--dur-base) var(--ease-out),
              border-radius var(--dur-base) var(--ease-out),
              transform 0s;
  min-height: 64px;
}
.cat-row.is-dragging {
  z-index: 10;
  background: var(--surface);
  box-shadow: var(--shadow-2);
  border-radius: var(--r-3);
  border-color: transparent;
  transform: translateY(0) scale(1.02);
}
.cat-drag-handle {
  width: var(--tap);
  height: var(--tap);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  cursor: grab;
  flex-shrink: 0;
  touch-action: none;
  margin: 0 -4px 0 -10px;
  border-radius: 8px;
  -webkit-user-select: none;
  user-select: none;
}
.cat-drag-handle:active {
  cursor: grabbing;
  color: var(--ink);
  background: var(--paper-2);
}
.cat-drag-handle svg { width: 24px; height: 24px; }

.cat-row-emoji {
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--surface);
  box-shadow: 0 0 0 2px color-mix(in oklch, var(--cat, var(--rule-strong)) 28%, var(--paper));
  flex-shrink: 0;
  font-size: 16px;
  line-height: 1;
}
.cat-row-name {
  flex: 1;
  font-family: var(--font-display);
  font-weight: var(--w-semi);
  font-size: 17px;
  color: var(--ink);
  letter-spacing: -0.005em;
  /* `capitalize` (not `none`) because EN defaults are stored lowercase
     ('produce', 'pantry') — capitalizing at display time renders them
     properly without a data migration. DE names are already capitalized
     so this is a no-op for them. */
  text-transform: capitalize;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cat-row-count {
  font-family: var(--font-sans);
  font-weight: var(--w-semi);
  font-size: var(--text-xs);
  letter-spacing: var(--track-cap);
  text-transform: uppercase;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.cat-row-edit {
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  color: var(--muted);
  flex-shrink: 0;
  min-width: var(--tap);
  min-height: var(--tap);
}
.cat-row-edit:active { background: var(--paper-2); color: var(--ink); }
.cat-row-edit svg { width: 18px; height: 18px; }

.empty-store {
  padding: 24px 24px 12px;
}
.empty-store h2 {
  font-size: 22px;
  margin-bottom: 6px;
}
.empty-store p { font-size: 14px; line-height: var(--lh-normal); }
/* Bouncing finger pointing down at the available-aisles chips —
   a friendly nudge for first-timers landing here from the tour. */
.empty-store-finger {
  display: inline-block;
  font-size: 36px;
  line-height: 1;
  margin-bottom: 10px;
  animation: empty-store-bounce 1.4s ease-in-out infinite;
  transform-origin: 50% 80%;
}
@keyframes empty-store-bounce {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(8px); }
}
@media (prefers-reduced-motion: reduce) {
  .empty-store-finger { animation: none; }
}

.aisles-available-section {
  margin-top: 8px;
  border-top: 1px solid var(--rule);
}
.aisles-available {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 0 16px 16px;
}
.aisle-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 40px;
  padding: 0 12px 0 10px;
  background: var(--paper-2);
  border: 1px solid var(--rule);
  /* Squashed elliptical pill — looks like a tag drawn freehand rather
     than the canonical infinite-radius capsule. */
  border-radius: var(--r-pill-soft);
  font-family: var(--font-sans);
  font-weight: var(--w-medium);
  font-size: var(--text-sm);
  color: var(--ink);
  min-height: var(--tap);
  transition: background var(--dur-quick) var(--ease-out),
              border-color var(--dur-quick) var(--ease-out),
              transform var(--dur-quick) var(--ease-out);
}
.aisle-chip:nth-child(even) { transform: rotate(0.4deg); }
.aisle-chip:nth-child(3n)   { transform: rotate(-0.6deg); }
.aisle-chip:active {
  background: var(--paper-3);
  transform: scale(0.97);
}
.aisle-chip-emoji {
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--surface);
  box-shadow: 0 0 0 2px color-mix(in oklch, var(--cat, var(--rule-strong)) 28%, var(--paper));
  font-size: 13px;
}
.aisle-chip-name { text-transform: capitalize; }
.aisle-chip-plus {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--tomato);
  flex-shrink: 0;
}

.store-footer {
  display: flex;
  gap: 10px;
  padding: var(--space-4) var(--space-4) var(--space-4);
}
.store-footer .primary-btn,
.store-footer .ghost-btn {
  flex: 1;
}

/* =============== Saved screen =============== */
.saved-header {
  padding: 20px 20px 8px;
}
.saved-header h2 {
  font-family: var(--font-display);
  font-weight: var(--w-bold);
  font-size: 28px;
  color: var(--ink);
  letter-spacing: var(--track-tight);
  margin-top: 4px;
}
.saved-list {
  padding: 12px 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.saved-row {
  background: var(--surface);
  border: 1.5px solid var(--rule);
  /* Uneven corners + alternating micro-tilts make a stack of saved
     lists look paper-clipped together instead of pixel-stacked. */
  border-radius: var(--r-sticker-sm);
  padding: 14px 16px;
  display: flex;
  align-items: center;
  gap: 14px;
  box-shadow:
    0 1px 0 color-mix(in oklch, var(--ink) 6%, transparent),
    0 6px 14px -8px rgba(0, 0, 0, 0.14);
  cursor: pointer;
  transform: rotate(-0.35deg);
  transform-origin: center;
  transition: background var(--dur-quick) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
  min-height: var(--tap);
}
.saved-row:nth-child(even) { transform: rotate(0.3deg); }
.saved-row:nth-child(3n)   { transform: rotate(-0.15deg); }
.saved-row:active { background: var(--paper-2); transform: rotate(0) scale(0.995); }
.saved-row-main { flex: 1; min-width: 0; }
.saved-row-name {
  font-family: var(--font-sans);
  font-weight: var(--w-bold);
  font-size: var(--text-lg);
  color: var(--ink);
  letter-spacing: -0.005em;
  margin-bottom: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.saved-row-meta {
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  color: var(--muted);
  margin-top: 2px;
}
.saved-row-dots {
  display: flex;
  gap: 3px;
  margin-top: 6px;
}
.saved-row-dots span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
}
.saved-row-actions {
  display: flex;
  gap: 4px;
  flex-shrink: 0;
}
.saved-row-arrow {
  color: var(--muted);
  width: var(--tap);
  height: var(--tap);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
}
.saved-row-arrow:active { background: var(--paper-2); color: var(--ink); }

/* =============== Buttons =============== */
.primary-btn, .ghost-btn, .danger-btn, .secondary-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: var(--tap);
  min-width: var(--tap);
  padding: 0 18px;
  border-radius: var(--r-2);
  font-family: var(--font-sans);
  font-weight: var(--w-semi);
  font-size: var(--text-md);
  letter-spacing: 0;
  transition: background var(--dur-quick) var(--ease-out),
              color var(--dur-quick) var(--ease-out);
  white-space: nowrap;
  border: 1px solid transparent;
}
.primary-btn { background: var(--tomato-500); color: #fff; }
.primary-btn:active { background: var(--tomato-600); }
.secondary-btn { background: var(--paper-2); color: var(--ink); border-color: var(--rule); }
.secondary-btn:active { background: var(--paper-3); }
.ghost-btn { background: transparent; color: var(--ink); border-color: var(--rule); }
.ghost-btn:active { background: var(--paper-2); }
/* The shared button rule above sets display, overriding the UA [hidden]
   rule — restore it so ghost buttons toggled via the `hidden` attribute
   (onboarding Back, store Publish) actually hide. */
.ghost-btn[hidden] { display: none; }
.danger-btn { background: transparent; color: var(--tomato-deep); border-color: var(--rule); }
.danger-btn:active { background: var(--tomato-100); }
.primary-btn.full, .ghost-btn.full, .secondary-btn.full { width: 100%; }

/* Hover states — only on devices with a real pointer so touch doesn't
   get sticky hover. The cursor pointer is a small affordance cue. */
@media (hover: hover) and (pointer: fine) {
  .primary-btn { cursor: pointer; }
  .primary-btn:hover { background: var(--tomato-400); }
  .secondary-btn { cursor: pointer; }
  .secondary-btn:hover { background: var(--paper-3); border-color: var(--rule-strong); }
  .ghost-btn { cursor: pointer; }
  .ghost-btn:hover { background: var(--paper-2); border-color: var(--rule-strong); }
  .danger-btn { cursor: pointer; }
  .danger-btn:hover { background: var(--tomato-50); border-color: var(--tomato-200); color: var(--tomato-600); }
  .link-btn { cursor: pointer; }
  .link-btn:hover { background: var(--paper-2); color: var(--tomato-600); }
  .link-btn.danger:hover { background: var(--tomato-50); color: var(--tomato-600); }
  .tab { cursor: pointer; }
  .tab:not(.is-active):hover { color: var(--ink); background: var(--paper-2); }
  .subtab { cursor: pointer; }
  .subtab:not(.is-active):hover { color: var(--ink); background: color-mix(in oklch, var(--paper) 60%, var(--paper-2)); }
  .aisle-chip { cursor: pointer; }
  .aisle-chip:hover {
    background: color-mix(in oklch, var(--cat, var(--tomato-500)) 14%, var(--paper));
    border-color: color-mix(in oklch, var(--cat, var(--tomato-500)) 50%, var(--rule));
    transform: translateY(-1px);
  }
  .settings-row { cursor: pointer; }
  .settings-row:hover { background: var(--paper-2); }
  .cat-row-edit { cursor: pointer; }
  .cat-row-edit:hover { background: var(--paper-2); color: var(--ink); }
  .sheet-row { cursor: pointer; }
  .sheet-row:hover { background: var(--paper-2); }
  .coachmark-close { cursor: pointer; }
  .coachmark-close:hover { background: var(--paper-2); color: var(--ink); }
  .install-prompt-actions .primary-btn:hover { background: var(--tomato-400); }
  .install-prompt-actions .ghost-btn:hover { background: var(--paper-2); }
}

/* =============== Tabbar — floating pill =============== */
.tabbar {
  position: fixed;
  left: 50%;
  bottom: calc(12px + var(--safe-bottom));
  transform: translateX(-50%);
  display: inline-flex;
  padding: 4px;
  gap: 2px;
  background: var(--paper-2);
  border: 1.5px solid var(--rule);
  /* Elliptical pill — wider corner radius vertically than horizontally
     gives the bar a slightly squat, drawn silhouette. */
  border-radius: 26px / 32px;
  box-shadow:
    var(--shadow-1),
    0 8px 24px -10px rgba(0, 0, 0, 0.18);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  z-index: 50;
  transition: transform var(--dur-base) var(--ease-out),
              opacity var(--dur-base) var(--ease-out);
  will-change: transform;
}
body.tabbar-hidden .tabbar {
  transform: translate(-50%, calc(100% + 28px));
  opacity: 0;
}
.tab {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  background: transparent;
  border: 0;
  color: var(--muted);
  font-family: var(--font-sans);
  font-weight: var(--w-semi);
  font-size: var(--text-sm);
  letter-spacing: 0;
  height: 38px;
  padding: 0 14px;
  border-radius: var(--r-pill);
  cursor: pointer;
  transition: background var(--dur-quick) var(--ease-out),
              color var(--dur-quick) var(--ease-out);
}
.tab svg { width: 16px; height: 16px; }
/* Icon-only variant — used for the Settings tab so the 4-tab bar (List,
   Library, Community, Settings) fits comfortably. The label is exposed via
   aria-label for screen readers. */
.tab.tab-icon-only { padding: 0 12px; gap: 0; }
.tab.tab-icon-only svg { width: 19px; height: 19px; }
/* Narrow phones (iPhone SE / Android equivalents) can't fit four tabs with
   labels in some translations — drop the rest to icons-only there too. */
@media (max-width: 420px) {
  .tab { padding: 0 10px; gap: 0; }
  .tab svg { width: 19px; height: 19px; }
  .tab span { display: none; }
}
.tab:active { background: var(--paper-3); }
.tab.is-active {
  background: var(--paper);
  color: var(--ink);
  box-shadow: var(--shadow-1);
}

/* =============== Sheets =============== */
.sheets-root { position: relative; }
.sheet {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}
.sheet[hidden] { display: none; }
.sheet-backdrop {
  position: absolute;
  inset: 0;
  /* Soft tinted blur over the page behind — less stark than a plain
     dark scrim, frames the sheet as a layered "card on glass". */
  background: rgba(26, 26, 26, 0.22);
  -webkit-backdrop-filter: blur(6px) saturate(120%);
  backdrop-filter: blur(6px) saturate(120%);
  animation: gf-fade var(--dur-base) var(--ease-out);
}
@supports not (backdrop-filter: blur(1px)) {
  .sheet-backdrop { background: rgba(26, 26, 26, 0.42); }
}
.sheet-content {
  position: relative;
  width: 100%;
  max-width: var(--max-w);
  max-height: 88vh;
  /* Frosted-glass surface — semi-transparent paper + strong blur of
     whatever's behind. Falls back to opaque paper on browsers without
     backdrop-filter (Safari < 14, older Firefox). */
  background: color-mix(in oklch, var(--paper) 86%, transparent);
  -webkit-backdrop-filter: blur(28px) saturate(160%);
  backdrop-filter: blur(28px) saturate(160%);
  /* Top corners curl at slightly different radii (22/26) — a torn-paper
     silhouette, not a CAD-rounded rectangle. Bottom corners stay square
     because they sit on the safe area. */
  border-radius: 22px 26px 0 0;
  display: flex;
  flex-direction: column;
  box-shadow: var(--shadow-sheet);
  animation: gf-sheet-up var(--dur-sheet) var(--ease-out);
  overflow: hidden;
}
@supports not (backdrop-filter: blur(1px)) {
  .sheet-content { background: var(--paper); }
}
/* Install sheet — true glass like onboarding (content-display, no form). */
.sheet[data-sheet="install"] .sheet-content {
  background: color-mix(in oklch, var(--paper) 36%, transparent);
  -webkit-backdrop-filter: blur(44px) saturate(190%);
  backdrop-filter: blur(44px) saturate(190%);
}
@supports not (backdrop-filter: blur(1px)) {
  .sheet[data-sheet="install"] .sheet-content { background: var(--paper); }
}
.sheet-handle {
  height: 14px;
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: pointer;
  flex-shrink: 0;
  padding-top: 6px;
}
.sheet-handle::before {
  content: "";
  width: 36px;
  height: 4px;
  border-radius: 2px;
  background: var(--rule-strong);
}
.sheet-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 4px 18px 14px;
  border-bottom: 1px solid var(--rule);
  flex-shrink: 0;
}
.sheet-title {
  font-family: var(--font-display);
  font-weight: var(--w-bold);
  font-size: 20px;
  color: var(--ink);
  letter-spacing: var(--track-tight);
  text-transform: none;
}
.sheet-close {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--paper-2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  min-width: var(--tap);
  min-height: var(--tap);
}
.sheet-close:active { background: var(--paper-3); color: var(--ink); }
.sheet-close svg { width: 16px; height: 16px; }
.sheet-sub {
  padding: 12px 18px 8px;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--muted);
  line-height: var(--lh-normal);
}
.sheet-body {
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  touch-action: pan-y;
  flex: 1 1 auto;
  min-height: 0;
}
/* Forms inside sheets wrap both .sheet-body and .sheet-footer. Without
   making the form itself a flex column, the sheet-body's `flex: 1 1 auto`
   has no effect — the form takes natural height and .sheet-body never
   gets a constrained height to scroll within. */
.sheet-content > form {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}
.sheet-footer {
  padding: 12px 16px calc(20px + var(--safe-bottom));
  display: flex;
  gap: 10px;
  border-top: 1px solid var(--rule);
  flex-shrink: 0;
  background: var(--paper);
}
.sheet-footer .primary-btn,
.sheet-footer .ghost-btn,
.sheet-footer .secondary-btn,
.sheet-footer .danger-btn {
  flex: 1;
  height: 48px;
  font-size: var(--text-md);
}
.sheet-footer .spacer { flex: 0; }

@keyframes gf-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes gf-sheet-up {
  from { transform: translateY(100%); }
  to   { transform: translateY(0); }
}

/* Generic sheet rows */
.sheet-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--rule);
  cursor: pointer;
  min-height: var(--tap);
  transition: background var(--dur-quick) var(--ease-out);
}
.sheet-row:active { background: var(--paper-2); }
.sheet-row-em {
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--surface);
  box-shadow: 0 0 0 2px color-mix(in oklch, var(--cat, var(--rule-strong)) 28%, var(--paper));
  flex-shrink: 0;
  font-size: 16px;
}
.sheet-row-name {
  flex: 1;
  font-family: var(--font-sans);
  font-weight: var(--w-regular);
  font-size: var(--text-md);
  color: var(--ink);
  text-transform: lowercase;
}
/* Store / list / language pickers reuse .store-switcher as the container.
   Store and list names are user-typed (e.g. "Trader Joe's", "Sunday shop")
   and look wrong lowercased — preserve the case the user gave them. */
.store-switcher .sheet-row-name { text-transform: none; }
.sheet-row.is-active .sheet-row-name {
  color: var(--tomato);
  font-weight: var(--w-medium);
}
.sheet-row-check {
  color: var(--tomato);
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.sheet-row-check svg { width: 20px; height: 20px; }
.sheet-row-action {
  height: 32px;
  padding: 0 12px;
  border-radius: var(--r-2);
  background: transparent;
  border: 1px solid var(--rule);
  font-family: var(--font-sans);
  font-weight: var(--w-semi);
  font-size: var(--text-sm);
  color: var(--ink);
  flex-shrink: 0;
}
.sheet-row-action:active { background: var(--paper-2); }

/* =============== Forms =============== */
.field {
  padding: 14px 18px;
}
.field-label {
  display: block;
  font-family: var(--font-sans);
  font-weight: var(--w-semi);
  font-size: var(--text-xs);
  letter-spacing: var(--track-cap);
  text-transform: uppercase;
  color: var(--muted);
  /* Clear breathing room between the label and the input — was 6px,
     which made the input feel glued to its caption. */
  margin-bottom: 12px;
}
.field-label small {
  text-transform: none;
  letter-spacing: 0;
  font-weight: var(--w-regular);
}
.field input[type="text"],
.field input[type="number"],
.field input[type="search"],
.field input[type="email"],
.field input[type="tel"],
.field textarea {
  width: 100%;
  height: 44px;
  padding: 0 12px;
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  background: var(--surface);
  font-family: var(--font-sans);
  font-weight: var(--w-regular);
  font-size: var(--text-md);
  color: var(--ink);
  color-scheme: light dark;
  transition: border-color var(--dur-quick) var(--ease-out),
              box-shadow var(--dur-quick) var(--ease-out);
}
/* Number input: hide the browser's spin buttons in iOS/desktop and ensure
   the caret + selected text colors track the theme. */
.field input[type="number"] {
  -moz-appearance: textfield;
  caret-color: var(--ink);
}
.field input[type="number"]::-webkit-outer-spin-button,
.field input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.field textarea {
  height: auto;
  padding: 12px;
  min-height: 76px;
  resize: vertical;
  line-height: var(--lh-normal);
}
.field input:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--tomato);
  box-shadow: var(--shadow-focus);
}
.emoji-input {
  width: 80px !important;
  text-align: center;
  font-size: 22px !important;
}
.field-row {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 10px 18px;
}
.field-row .field { padding: 0; }
.flex-1 { flex: 1; min-width: 0; }
.flex-narrow { width: 110px; flex-shrink: 0; }

/* =============== Pickers =============== */
.cat-picker {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 0 18px 8px;
}
.cat-pick {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 32px;
  padding: 0 12px;
  background: var(--paper-2);
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  font-family: var(--font-sans);
  font-weight: var(--w-medium);
  font-size: var(--text-sm);
  color: var(--ink);
  min-height: var(--tap);
  margin: 4px 0;
  transition: background var(--dur-quick) var(--ease-out),
              color var(--dur-quick) var(--ease-out),
              border-color var(--dur-quick) var(--ease-out);
}
.cat-pick:active { background: var(--paper-3); }
.cat-pick.is-active {
  background: var(--tomato);
  color: #fff;
  border-color: var(--tomato);
}
.cat-pick-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--cat, var(--muted-2));
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06);
}

.color-picker {
  display: flex;
  gap: 12px;
  padding: 0 0 4px;
  flex-wrap: wrap;
}
.color-swatch {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--c, var(--ink));
  border: 2px solid var(--paper);
  box-shadow: 0 0 0 1px var(--rule);
  min-width: var(--tap);
  min-height: var(--tap);
  position: relative;
  transition: box-shadow var(--dur-quick) var(--ease-out);
}
.color-swatch.is-active {
  box-shadow: 0 0 0 2px var(--ink);
}
/* Auto-derived swatch (from emoji) gets a subtle sparkle marker in
   the top-right corner so the user can tell it apart from the
   curated palette below. */
.color-swatch.is-auto::after {
  content: "✨";
  position: absolute;
  top: -4px;
  right: -4px;
  font-size: 12px;
  line-height: 1;
  filter: drop-shadow(0 1px 1px rgba(0,0,0,0.25));
  pointer-events: none;
}

/* =============== Share =============== */
.share-url-box {
  display: flex;
  gap: 8px;
  align-items: stretch;
  padding: 4px 18px 8px;
}
.share-url {
  flex: 1;
  min-width: 0;
  background: var(--paper-2);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  padding: 12px 14px;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--ink-soft);
  display: flex;
  align-items: center;
  word-break: break-all;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* =============== Toast =============== */
.toast {
  position: fixed;
  bottom: calc(var(--tabbar-h) + 12px + var(--safe-bottom));
  left: 50%;
  transform: translateX(-50%);
  background: var(--ink);
  color: var(--paper);
  padding: 4px 4px 4px 14px;
  border-radius: var(--r-pill);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: var(--w-medium);
  box-shadow: var(--shadow-1);
  z-index: 200;
  pointer-events: auto;
  cursor: pointer;
  opacity: 0.92;
  animation: gf-toast-in var(--dur-base) var(--ease-out);
  max-width: calc(100% - 32px);
  display: inline-flex;
  align-items: center;
  gap: 8px;
  transition: bottom var(--dur-base) var(--ease-out),
              opacity var(--dur-base) var(--ease-out);
}
body.tabbar-hidden .toast {
  bottom: calc(12px + var(--safe-bottom));
}
.toast-text {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 60vw;
}
.toast-undo {
  pointer-events: auto;
  background: transparent;
  color: var(--tomato);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: var(--w-bold);
  letter-spacing: 0.02em;
  padding: 6px 12px;
  border-radius: var(--r-pill);
  border: 0;
  margin-left: 2px;
  min-height: 28px;
  transition: background var(--dur-quick) var(--ease-out);
}
.toast-undo:active { background: rgba(255, 255, 255, 0.12); }
.toast-undo[hidden] { display: none; }
/* Generic action button on the toast — used for the SW "new version
   available — Reload?" prompt, and any future toast that needs a
   non-undo CTA. Same visual weight as .toast-undo. */
.toast-action {
  pointer-events: auto;
  background: transparent;
  color: var(--tomato);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: var(--w-bold);
  letter-spacing: 0.02em;
  padding: 6px 12px;
  border-radius: var(--r-pill);
  border: 0;
  margin-left: 2px;
  min-height: 28px;
  transition: background var(--dur-quick) var(--ease-out);
}
.toast-action:active { background: rgba(255, 255, 255, 0.12); }
.toast-action[hidden] { display: none; }
.toast[hidden] { display: none; }
@keyframes gf-toast-in {
  from { opacity: 0; transform: translate(-50%, 8px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}

/* =============== Reduced motion =============== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .sheet-backdrop, .sheet-content, .toast { animation: none; }
}

/* =============== Tablet+ tweaks =============== */
@media (min-width: 720px) {
  .add-form { margin-left: var(--space-5); margin-right: var(--space-5); }
  .item-row { padding-left: 24px; padding-right: 24px; }
  .cat-header { padding-left: 24px; padding-right: 24px; }
}

/* Hidden helper sprite (when icons.svg is inlined) */
.icons-sprite { display: none; }

/* Inline icon helper */
svg.icon { width: 1em; height: 1em; fill: currentColor; }

/* =============== List header (iOS large-title style) =============== */
.list-header {
  margin: 8px 16px 6px;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 8px;
}
.list-header-text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-width: 0;
  flex: 1;
}
.list-title-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: transparent;
  border: 0;
  padding: 0;
  color: var(--ink);
  font-family: var(--font-display);
  font-weight: var(--w-bold);
  font-size: 28px;
  line-height: 1.1;
  letter-spacing: var(--track-tight);
  max-width: 100%;
  cursor: pointer;
}
.list-title-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.list-title-chevron {
  width: 16px;
  height: 16px;
  color: var(--muted);
  flex-shrink: 0;
  margin-top: 4px;
}
.list-subtitle-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: transparent;
  border: 0;
  padding: 2px 0;
  margin-top: 2px;
  color: var(--muted);
  font-family: var(--font-sans);
  font-weight: var(--w-medium);
  font-size: var(--text-sm);
  max-width: 100%;
  cursor: pointer;
}
.list-subtitle-btn:active { color: var(--ink); }
.list-subtitle-icon { width: 13px; height: 13px; flex-shrink: 0; }
.list-subtitle-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.list-subtitle-chevron { width: 11px; height: 11px; flex-shrink: 0; opacity: 0.7; }
.list-header-actions {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  flex-shrink: 0;
}
.list-pill-count {
  font-size: var(--text-xs);
  font-weight: var(--w-semi);
  letter-spacing: var(--track-cap);
  text-transform: uppercase;
  color: var(--muted);
  margin-right: 4px;
}

/* =============== Search =============== */
.search-toggle {
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-pill);
  background: transparent;
  border: 0;
  color: var(--muted);
  cursor: pointer;
  transition: background var(--dur-quick) var(--ease-out),
              color var(--dur-quick) var(--ease-out);
}
.search-toggle:active { background: var(--paper-2); }
.search-toggle svg { width: 18px; height: 18px; }
.search-toggle.is-active {
  background: var(--paper-2);
  color: var(--ink);
}
.search-bar {
  margin: 10px 16px 0;
  display: flex;
  align-items: center;
  gap: 8px;
  height: 36px;
  padding: 0 10px 0 12px;
  background: var(--paper-2);
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  animation: gf-search-in var(--dur-base) var(--ease-out);
}
.search-bar[hidden] { display: none; }
.search-bar-icon {
  width: 14px;
  height: 14px;
  color: var(--muted);
  flex-shrink: 0;
}
.search-input {
  flex: 1;
  min-width: 0;
  height: 100%;
  border: 0;
  background: transparent;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--ink);
  outline: none;
  padding: 0;
}
.search-input::placeholder { color: var(--muted-2); }
.search-input::-webkit-search-decoration,
.search-input::-webkit-search-cancel-button,
.search-input::-webkit-search-results-button,
.search-input::-webkit-search-results-decoration {
  -webkit-appearance: none;
  appearance: none;
  display: none;
}
.search-clear {
  width: 24px;
  height: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: var(--paper-3);
  color: var(--muted);
  border-radius: var(--r-pill);
  cursor: pointer;
  flex-shrink: 0;
  transition: background var(--dur-quick) var(--ease-out);
}
.search-clear:active { background: var(--rule); }
.search-clear[hidden] { display: none; }
.search-clear svg { width: 12px; height: 12px; }
.search-empty {
  padding: 48px 24px;
  text-align: center;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--muted);
}
@keyframes gf-search-in {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .search-bar { animation: none; }
}

/* =============== Read-only banner =============== */
.readonly-banner {
  margin: 8px 16px 0;
  padding: 10px 14px;
  background: var(--paper-2);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  font-size: var(--text-sm);
  color: var(--muted);
  font-weight: var(--w-medium);
  display: flex;
  align-items: center;
  gap: 8px;
}
/* Without this the `display: flex` above wins over the browser's
   built-in `[hidden] { display: none }`, so el.hidden = true does
   nothing and the banner shows on every list — not just read-only ones. */
.readonly-banner[hidden] { display: none; }
.readonly-banner svg { width: 14px; height: 14px; color: var(--muted); }
[data-readonly="1"] .add-form,
[data-readonly="1"] .list-toolbar,
[data-readonly="1"] .item-edit-btn,
[data-readonly="1"] .qty-stepper,
[data-readonly="1"] .item-row[data-action="toggle-item"] { pointer-events: none; opacity: 0.85; }
[data-readonly="1"] .add-input { background: var(--paper-2); }

/* =============== Long-press hint =============== */
.item-row.is-long-pressing {
  background: var(--paper-2);
  transform: scale(0.99);
  transition: background 80ms var(--ease-out), transform 80ms var(--ease-out);
}

/* =============== Swipe gestures =============== */
.item-row.is-swiping { transition: none; will-change: transform; }

/* Both action strips are anchored *outside* the row (right:100% for the
   delete strip on the left, left:100% for the edit strip on the right)
   and run full-width. The items-list's overflow:clip hides them by
   default; as the row translates, more of the strip enters the visible
   area — a natural, smooth reveal that scales with finger movement
   instead of popping in at a threshold.
   The inner-facing corners are rounded to match the items-list's outer
   card shape, so the strip reads as a pill peeking out from behind the
   row. The outer-facing edges sit beyond the clip so their corners are
   never seen. */
.item-row::before,
.item-row::after {
  content: "";
  position: absolute;
  top: 0; bottom: 0;
  width: 100vw;
  display: flex;
  align-items: center;
  color: #fff;
  font-size: 24px;
  line-height: 1;
  pointer-events: none;
  /* Slight pop-up of the icon when the user has swiped past the commit
     threshold; provides a visual cue that releasing now will fire the
     action. The class is toggled in JS at dx == ±100. */
  transition: padding var(--dur-quick) var(--ease-out),
              background-position var(--dur-quick) var(--ease-out),
              background-size var(--dur-quick) var(--ease-out);
}
/* Delete side — uses the same stroked trash icon as the SVG sprite so it
   matches the tab-bar / button iconography rather than the emoji 🗑. */
.item-row::before {
  right: 100%;
  background-color: var(--tomato);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 7h16M9 7V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2'/%3E%3Cpath d='M6 7l1 12a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-12'/%3E%3Cpath d='M10 11v6M14 11v6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 22px center;
  background-size: 26px 26px;
  border-radius: 0 14px 14px 0;
}
.item-row::after {
  left: 100%;
  background: var(--ink-soft, #555);
  content: "✎";
  justify-content: flex-start;
  padding-left: 22px;
  border-radius: 14px 0 0 14px;
}
/* At commit distance, nudge the icon further from the row's edge so it
   reads as "primed". JS toggles these classes when |dx| ≥ 100. */
.item-row.swipe-show-right::before { background-position: right 30px center; background-size: 30px 30px; }
.item-row.swipe-show-left::after   { padding-left:  30px; font-size: 26px; }

/* =============== Quantity stepper inline =============== */
.qty-stepper {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  width: 54px;
  flex-shrink: 0;
  height: 32px;
}
.qty-stepper-btn {
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--paper-2);
  color: var(--ink);
  font-weight: var(--w-bold);
  font-size: 14px;
  line-height: 1;
}
.qty-stepper-btn:active { background: var(--paper-3); }
.item-row .item-qty.has-stepper {
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 32px;
  border-radius: var(--r-2);
}
.item-row .item-qty.is-editing {
  background: var(--paper-2);
  outline: 1px solid var(--rule-strong);
}

/* =============== Out-of-stock indicator =============== */
.item-row.is-unavailable .item-name {
  text-decoration: line-through;
  text-decoration-color: var(--muted-2);
  color: var(--muted-2);
}
.item-row.is-unavailable .item-qty,
.item-row.is-unavailable .item-note { color: var(--muted-2); }
.item-row.is-unavailable .item-check {
  background: repeating-linear-gradient(
    -45deg,
    var(--paper-2),
    var(--paper-2) 3px,
    var(--paper-3) 3px,
    var(--paper-3) 6px
  );
}
.unavailable-badge {
  display: inline-flex;
  align-items: center;
  height: 18px;
  padding: 0 6px;
  border-radius: var(--r-1);
  background: var(--paper-2);
  border: 1px solid var(--rule);
  font-size: 10px;
  font-weight: var(--w-semi);
  letter-spacing: var(--track-cap);
  text-transform: uppercase;
  color: var(--muted);
  flex-shrink: 0;
}

/* =============== Sub-tabs (Saved screen) =============== */
.subtabs {
  display: inline-flex;
  background: var(--paper-2);
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  padding: 4px;
  margin: 0 16px 12px;
}
.subtab {
  height: 32px;
  padding: 0 14px;
  border-radius: var(--r-pill);
  background: transparent;
  font-family: var(--font-sans);
  font-weight: var(--w-semi);
  font-size: var(--text-sm);
  color: var(--muted);
}
.subtab.is-active {
  background: var(--paper);
  color: var(--ink);
  box-shadow: var(--shadow-1);
}
/* A subtab marked "coming soon" — small inline pill next to the label. */
.subtab.has-coming-soon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
/* Link-style toolbar button marked coming-soon (e.g. Import on the
   list toolbar). The text dims so it reads as "not yet"; the badge
   handles the actual "coming soon" signal. */
.link-btn.has-coming-soon {
  opacity: 0.7;
}
.link-btn.has-coming-soon .subtab-badge {
  font-size: 8.5px;
  padding: 1px 5px;
}
.subtab-badge {
  font-family: var(--font-mono);
  font-weight: var(--w-bold);
  font-size: 9px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 2px 6px;
  border-radius: 999px;
  background: color-mix(in oklch, var(--tomato) 18%, var(--paper));
  color: var(--tomato);
  border: 1px solid color-mix(in oklch, var(--tomato) 30%, transparent);
  line-height: 1;
}
/* A label-row that wraps the title + an inline "soon" badge so the
   badge sits on the same baseline as the row title. */
.toggle-row-label-row {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
/* A toggle/list row whose action is gated behind "coming soon".
   The text dims slightly and the checkbox is shown disabled. */
.toggle-row.is-coming-soon .toggle-row-label,
.settings-row.is-coming-soon .settings-row-value {
  opacity: 0.7;
}
.toggle-row.is-coming-soon input[type="checkbox"]:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* =============== Coachmark tour ===============
   A floating tooltip ("coachmark") + a spotlight cutout that points
   at a real UI element during the post-onboarding tour. The spotlight
   is a transparent rect with a 9999px box-shadow that fills the rest
   of the viewport with a dim scrim — the target stays interactive
   through the hole. The coachmark card sits adjacent with a diamond
   arrow that visually links it to the target. */
/* Full-screen backdrop blur during the tour. A polygon clip-path
   cuts a rectangular hole around the spotlit target so the target
   itself stays crisp while the rest of the page reads as soft
   frosted glass. CSS vars --sx1 / --sy1 / --sx2 / --sy2 are set
   by positionCoachmark() to match the spotlight rect. The polygon
   uses a "bridge" cut from the left edge into the inner rect so
   the whole thing is one continuous CW path that subtracts the
   hole via non-zero winding. */
.tour-blur {
  position: fixed;
  inset: 0;
  z-index: 8998;
  pointer-events: none;
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  backdrop-filter: blur(10px) saturate(140%);
  background: color-mix(in oklch, var(--ink) 8%, transparent);
  transition: clip-path 220ms var(--ease-out),
              -webkit-clip-path 220ms var(--ease-out);
  clip-path: polygon(
    0% 0%, 100% 0%, 100% 100%, 0% 100%,
    0px var(--sy1, -1px),
    var(--sx1, -1px) var(--sy1, -1px),
    var(--sx1, -1px) var(--sy2, -1px),
    var(--sx2, -1px) var(--sy2, -1px),
    var(--sx2, -1px) var(--sy1, -1px),
    var(--sx1, -1px) var(--sy1, -1px),
    0px var(--sy1, -1px),
    0% 0%
  );
  -webkit-clip-path: polygon(
    0% 0%, 100% 0%, 100% 100%, 0% 100%,
    0px var(--sy1, -1px),
    var(--sx1, -1px) var(--sy1, -1px),
    var(--sx1, -1px) var(--sy2, -1px),
    var(--sx2, -1px) var(--sy2, -1px),
    var(--sx2, -1px) var(--sy1, -1px),
    var(--sx1, -1px) var(--sy1, -1px),
    0px var(--sy1, -1px),
    0% 0%
  );
}
@supports not (backdrop-filter: blur(1px)) {
  .tour-blur {
    /* No backdrop-filter support — fall back to a darker scrim so
       the dimming still telegraphs "the rest of the page is paused". */
    background: rgba(26, 26, 26, 0.35);
  }
}

.tour-spotlight {
  position: fixed;
  border-radius: 10px;
  box-shadow:
    0 0 0 9999px color-mix(in oklch, var(--ink) 35%, transparent),
    0 0 0 3px color-mix(in oklch, var(--tomato) 70%, transparent);
  pointer-events: none;
  z-index: 9000;
  transition: left 220ms var(--ease-out), top 220ms var(--ease-out),
              width 220ms var(--ease-out), height 220ms var(--ease-out);
}
.coachmark {
  position: fixed;
  z-index: 9001;
  width: min(300px, calc(100vw - 28px));
  /* True glass — minimal paper tint + strong blur of the page behind. */
  background: color-mix(in oklch, var(--paper) 38%, transparent);
  -webkit-backdrop-filter: blur(38px) saturate(190%);
  backdrop-filter: blur(38px) saturate(190%);
  color: var(--ink);
  border-radius: 14px;
  padding: 14px 16px 12px;
  box-shadow:
    0 1px 0 color-mix(in oklch, var(--paper) 70%, transparent) inset,
    0 18px 40px rgba(0, 0, 0, 0.32);
  border: 1px solid color-mix(in oklch, var(--ink) 14%, transparent);
  font-family: var(--font-sans);
  animation: coachmark-pop 240ms var(--ease-out);
}
@supports not (backdrop-filter: blur(1px)) {
  .coachmark { background: var(--paper); }
}
@keyframes coachmark-pop {
  from { opacity: 0; transform: translateY(4px) scale(0.97); }
  to   { opacity: 1; transform: none; }
}
.coachmark-arrow {
  position: absolute;
  width: 14px;
  height: 14px;
  background: color-mix(in oklch, var(--paper) 38%, transparent);
  -webkit-backdrop-filter: blur(38px) saturate(190%);
  backdrop-filter: blur(38px) saturate(190%);
  border: 1px solid color-mix(in oklch, var(--ink) 14%, transparent);
  transform: translateX(-50%) rotate(45deg);
}
@supports not (backdrop-filter: blur(1px)) {
  .coachmark-arrow { background: var(--paper); }
}
.coachmark--below .coachmark-arrow {
  top: -7px;
  border-right: 0;
  border-bottom: 0;
}
.coachmark--above .coachmark-arrow {
  bottom: -7px;
  border-left: 0;
  border-top: 0;
}
.coachmark-close {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 28px;
  height: 28px;
  border-radius: 999px;
  border: 0;
  background: transparent;
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.coachmark-close:active { background: var(--paper-2); }
.coachmark-close svg { width: 14px; height: 14px; }
.coachmark-step {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: var(--w-bold);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--tomato);
  margin: 0 0 4px;
}
.coachmark-text {
  font-size: var(--text-sm);
  line-height: var(--lh-normal);
  color: var(--ink);
  margin: 0 0 10px;
  padding-right: 28px; /* avoid the X */
}
.coachmark-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}
.coachmark-next {
  height: 32px;
  padding: 0 14px;
  font-size: var(--text-sm);
  font-weight: var(--w-semi);
}
@media (prefers-reduced-motion: reduce) {
  .coachmark { animation: none; }
  .tour-spotlight { transition: none; }
}

/* =============== Smart install prompt ===============
   Non-blocking bottom card that slides up on the user's 2nd+
   visit. Sits above the bottom nav. Two actions: Install +
   Not now. Dismissed once → never shown again. */
.install-prompt {
  position: fixed;
  left: 12px;
  right: 12px;
  bottom: calc(80px + var(--safe-bottom));
  z-index: 8500;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px 14px 12px;
  /* True glass — minimal paper tint + strong blur of the list behind. */
  background: color-mix(in oklch, var(--paper) 38%, transparent);
  -webkit-backdrop-filter: blur(40px) saturate(190%);
  backdrop-filter: blur(40px) saturate(190%);
  border-radius: 16px;
  border: 1px solid color-mix(in oklch, var(--ink) 14%, transparent);
  box-shadow:
    0 1px 0 color-mix(in oklch, var(--paper) 70%, transparent) inset,
    0 14px 36px rgba(0, 0, 0, 0.24);
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 280ms var(--ease-out), transform 280ms var(--ease-out);
  pointer-events: none;
  max-width: 480px;
  margin: 0 auto;
}
@supports not (backdrop-filter: blur(1px)) {
  .install-prompt { background: var(--paper); }
}
.install-prompt.is-visible {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}
.install-prompt-body {
  display: flex;
  align-items: center;
  gap: 12px;
}
.install-prompt-icon {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: 10px;
  background: var(--paper-2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.install-prompt-icon img { display: block; }
.install-prompt-text {
  flex: 1;
  min-width: 0;
}
.install-prompt-title {
  margin: 0;
  font-family: var(--font-sans);
  font-weight: var(--w-semi);
  font-size: var(--text-md);
  color: var(--ink);
  letter-spacing: var(--track-tight);
}
.install-prompt-sub {
  margin: 2px 0 0;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--muted);
}
.install-prompt-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}
.install-prompt-actions .ghost-btn,
.install-prompt-actions .primary-btn {
  height: 34px;
  padding: 0 14px;
  font-size: var(--text-sm);
}
@media (prefers-reduced-motion: reduce) {
  .install-prompt { transition: none; }
}

/* Large standalone pill above an empty-state title. */
.coming-soon-pill {
  display: inline-block;
  font-family: var(--font-mono);
  font-weight: var(--w-bold);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 4px 10px;
  border-radius: 999px;
  background: color-mix(in oklch, var(--tomato) 14%, var(--paper));
  color: var(--tomato);
  border: 1px solid color-mix(in oklch, var(--tomato) 28%, transparent);
  margin-bottom: 10px;
}

/* =============== Toggle (used in Display sheet) =============== */
.toggle-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--rule);
}
.toggle-row-label {
  flex: 1;
  font-family: var(--font-sans);
  font-weight: var(--w-medium);
  font-size: var(--text-md);
  color: var(--ink);
}
.toggle-segmented {
  display: inline-flex;
  background: var(--paper-2);
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  padding: 3px;
}
.toggle-segmented button {
  height: 30px;
  padding: 0 12px;
  border-radius: var(--r-pill);
  font-family: var(--font-sans);
  font-weight: var(--w-semi);
  font-size: var(--text-xs);
  color: var(--muted);
  background: transparent;
  letter-spacing: 0.02em;
}
.toggle-segmented button.is-active {
  background: var(--paper);
  color: var(--ink);
  box-shadow: var(--shadow-1);
}

/* =============== Section divider in sheets =============== */
.sheet-section {
  font-family: var(--font-sans);
  font-weight: var(--w-semi);
  font-size: var(--text-xs);
  letter-spacing: var(--track-cap);
  text-transform: uppercase;
  color: var(--muted-2);
  padding: 26px 18px 14px;
  border-top: 1px solid var(--rule);
}
.sheet-section:first-of-type { border-top: 0; padding-top: 10px; }
/* sheet-sub variant used inside the Settings sheet, where it sits
   directly under a .sheet-section header. Hugs the header (no top
   padding) and leaves a clear gap to the first field below. */
.sheet-sub--in-settings {
  padding-top: 0;
  padding-bottom: 18px;
}

/* =============== Screen titles (iOS large-title) =============== */
.screen-title {
  font-family: var(--font-display);
  font-weight: var(--w-bold);
  font-size: 28px;
  letter-spacing: var(--track-tight);
  line-height: 1.1;
  color: var(--ink);
  margin: 8px 16px 14px;
}

/* Settings is a half-height overlay sheet — changes (theme/density/
   background) need to be visible live on the list peeking through. */
.sheet-settings .sheet-content { max-height: 72vh; }
.sheet-settings .sheet-backdrop {
  background: rgba(26, 26, 26, 0.10);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}
@media (prefers-color-scheme: dark) {
  .sheet-settings .sheet-backdrop { background: rgba(0, 0, 0, 0.16); }
}
:root[data-theme="dark"] .sheet-settings .sheet-backdrop { background: rgba(0, 0, 0, 0.16); }

/* =============== Subtab panels (Library) =============== */
.subtab-panel[hidden] { display: none; }
.subtab-panel { padding-bottom: var(--space-3); }

/* The shared .subtabs container is laid out as a segmented control;
   spread it across the row in the Library so all three pills fit. */
.screen-library .subtabs {
  display: flex;
  margin: 0 16px 14px;
}
.screen-library .subtab {
  flex: 1;
  text-align: center;
}

/* =============== Settings screen =============== */
.settings-section {
  margin: 0 0 28px;
}
/* Squash the bottom margin on the LAST section so the version
   number / footer doesn't float far below it. */
.sheet-settings .settings-section:last-of-type { margin-bottom: 14px; }
.settings-section-title {
  font-family: var(--font-sans);
  font-weight: var(--w-semi);
  font-size: var(--text-xs);
  letter-spacing: var(--track-cap);
  text-transform: uppercase;
  color: var(--muted-2);
  margin: 0 18px 8px;
}
.settings-section-sub {
  padding: 0 18px 6px;
  margin: 0;
}
.settings-row {
  display: flex;
  align-items: center;
  width: 100%;
  background: var(--paper-2);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  padding: 12px 14px;
  margin: 0 16px;
  width: calc(100% - 32px);
  gap: 10px;
  cursor: pointer;
  transition: background var(--dur-quick) var(--ease-out);
  font-family: var(--font-sans);
  color: var(--ink);
}
.settings-row:active { background: var(--paper-3); }
.settings-row-label {
  font-weight: var(--w-medium);
  font-size: var(--text-sm);
}
.settings-row-value {
  flex: 1;
  color: var(--ink);
  font-weight: var(--w-medium);
  font-size: var(--text-sm);
}
.settings-row-chevron {
  width: 14px;
  height: 14px;
  color: var(--muted);
  flex-shrink: 0;
}
.settings-row-icon {
  width: 18px;
  height: 18px;
  color: var(--muted);
  flex-shrink: 0;
}
.settings-row + .settings-row { margin-top: 6px; }
/* Second line on a toggle row — describes what the toggle actually does
   when the label alone isn't self-explanatory (geofence, history badge). */
.toggle-row-sub {
  display: block;
  margin-top: 2px;
  font-weight: var(--w-regular);
  font-size: var(--text-xs);
  color: var(--muted);
  line-height: 1.35;
}
/* Barcode-scan sheet stage: a 1:1 black box that holds the live video
   feed and a centered reticle. Height is capped so the sheet still fits
   on small phones with the footer visible. */
.scan-stage {
  position: relative;
  width: calc(100% - 36px);
  margin: 4px 18px 12px;
  aspect-ratio: 1 / 1;
  max-height: 44vh;
  border-radius: var(--r-3);
  overflow: hidden;
  background: #000;
}
.scan-video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.scan-reticle {
  position: absolute;
  inset: 18%;
  border: 2px solid color-mix(in oklch, var(--tomato) 80%, white);
  border-radius: var(--r-2);
  box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.45);
  pointer-events: none;
}
.scan-status {
  text-align: center;
  padding: 0 18px 8px;
  min-height: 18px;
}
.settings-version {
  text-align: center;
  padding: 14px 0 4px;
  opacity: 0.45;
  font-size: 11px;
  font-family: monospace;
  margin: 0;
}

/* =============== Account section (Phase 2a) =============== */
.account-pane[hidden] { display: none; }
.account-step[hidden] { display: none; }
/* Inline status line under email / code inputs — green on success,
   tomato on error. Reused for "code sent to …", "wrong code", etc. */
.account-msg {
  padding: 4px 18px 8px;
  min-height: 14px;
  font-size: var(--text-xs);
  color: var(--muted);
}
.account-msg.is-error { color: var(--tomato-deep); }
.account-msg.is-success { color: color-mix(in oklch, var(--olive) 60%, var(--ink)); }
.account-email-display {
  margin: 0;
  padding: 6px 18px 4px;
  font-family: var(--font-sans);
  font-weight: var(--w-semi);
  font-size: var(--text-md);
  color: var(--ink);
  word-break: break-all;
}
/* "Synced 3s ago" / "Synced 4m ago" / "Not synced yet" — quiet status
   under the email so the user can verify the sync engine is alive. */
.account-sync-status {
  margin: 0;
  padding: 0 18px 8px;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  color: var(--muted);
  min-height: 14px;
}
#account-code-input {
  font-family: var(--font-mono);
  letter-spacing: 0.2em;
  font-size: 18px;
  text-align: center;
}
#account-send-code-btn[disabled],
#account-verify-btn[disabled] {
  opacity: 0.55;
  pointer-events: none;
}

/* =============== Import-from-AI sheet =============== */
.import-step {
  display: flex;
  gap: 12px;
  padding: 14px 18px 0;
}
.import-step + .import-step { padding-top: 18px; }
.import-step-num {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-sans);
  font-weight: var(--w-bold);
  font-size: var(--text-xs);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-top: 2px;
}
.import-step-body {
  flex: 1;
  min-width: 0;
}
.import-step-title {
  font-family: var(--font-sans);
  font-weight: var(--w-semi);
  font-size: var(--text-sm);
  color: var(--ink);
  margin: 0 0 8px;
}
.import-prompt-box {
  position: relative;
  background: var(--paper-2);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  padding: 12px 12px 44px;
}
.import-prompt-text {
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 12px;
  line-height: 1.45;
  color: var(--ink);
  white-space: pre-wrap;
  word-break: break-word;
  margin: 0;
}
.import-copy-btn {
  position: absolute;
  right: 8px;
  bottom: 8px;
  height: 28px;
  padding: 0 12px;
  font-size: var(--text-xs);
}
.import-paste {
  width: 100%;
  min-height: 140px;
  padding: 12px;
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  background: var(--surface);
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: var(--text-sm);
  color: var(--ink);
  resize: vertical;
  line-height: var(--lh-normal);
  outline: none;
  transition: border-color var(--dur-quick) var(--ease-out),
              box-shadow var(--dur-quick) var(--ease-out);
}
.import-paste:focus {
  border-color: var(--tomato);
  box-shadow: var(--shadow-focus);
}
.import-preview-meta {
  margin: 8px 0 0;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  color: var(--muted);
  min-height: 1em;
}
.import-preview-meta.has-items { color: var(--olive-deep); font-weight: var(--w-semi); }

/* ============================================================
   ONBOARDING — first-run walkthrough
   ----
   A rounded bottom-sheet popup (90vh) with a horizontal slide
   track. Each slide is the same width as the viewport; .onb-track
   shifts via translateX to bring the active slide into view.
   The backdrop is a frosted blur (no close action wired) so users
   can see the app paused behind them but can't dismiss by tapping
   outside — Skip / Back / Next are the only way out.
   ============================================================ */

.sheet-onboarding {
  /* Onboarding is a guided modal — visually it's a normal rounded
     bottom sheet, but it can't be dismissed by tapping the backdrop
     or the handle (those elements have no close action). The user
     must use Skip or step through to the end. */
  align-items: flex-end;
}
.sheet-onboarding .sheet-backdrop {
  /* Frosted-glass background so the app behind reads as "paused"
     without being completely hidden. Falls back to a darker rgba
     on browsers without backdrop-filter support. */
  background: color-mix(in oklch, var(--paper) 35%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  backdrop-filter: blur(18px) saturate(140%);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .sheet-onboarding .sheet-backdrop {
    background: rgba(26, 26, 26, 0.42);
  }
}
.sheet-onboarding .sheet-content {
  /* Floating card — lifted off the bottom edge so all four corners
     are rounded the same way. The container's align-items:flex-end
     pins it down, and margin-bottom pushes it back up into view. */
  max-height: 86vh;
  height: 86vh;
  border-radius: 20px;
  margin: 0 14px calc(14px + var(--safe-bottom));
  width: calc(100% - 28px);
  /* True glass — minimal paper tint, dominant blur. The shader
     gradient + any list content behind reads through as a soft
     textured background, not a white card. Ink text still reads
     because the 32% paper wash + strong blur creates uniform tone. */
  background: color-mix(in oklch, var(--paper) 32%, transparent);
  -webkit-backdrop-filter: blur(48px) saturate(190%);
  backdrop-filter: blur(48px) saturate(190%);
  /* Inner highlight + outer shadow give the glass card depth. */
  border: 1px solid color-mix(in oklch, var(--ink) 12%, transparent);
  box-shadow:
    0 1px 0 color-mix(in oklch, var(--paper) 70%, transparent) inset,
    0 -1px 0 color-mix(in oklch, var(--paper) 40%, transparent),
    0 24px 60px rgba(0, 0, 0, 0.32);
}
@supports not (backdrop-filter: blur(1px)) {
  .sheet-onboarding .sheet-content { background: var(--paper); }
}
/* Footer no longer needs to clear the home indicator — the
   sheet's bottom margin already absorbs the safe area. */
.sheet-onboarding .onb-footer {
  padding-bottom: 14px;
}
.sheet-onboarding .sheet-handle {
  /* Decorative pill — no close action wired to it. */
  cursor: default;
}

/* ---------- top bar: progress dots + skip ---------- */
.onb-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 4px 18px 10px;
  flex-shrink: 0;
}
.onb-progress {
  display: flex;
  gap: 6px;
  align-items: center;
}
.onb-progress-dot {
  width: 18px;
  height: 4px;
  border-radius: 2px;
  background: var(--rule-strong);
  transition: background var(--dur-base) var(--ease-out),
              width var(--dur-base) var(--ease-out);
}
.onb-progress-dot.is-active {
  background: var(--tomato);
  width: 28px;
}
.onb-progress-dot.is-done {
  background: color-mix(in oklch, var(--tomato) 60%, var(--paper));
}
.onb-skip {
  background: transparent;
  border: none;
  color: var(--muted);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  padding: 8px 10px;
  border-radius: 8px;
  cursor: pointer;
  min-height: 32px;
}
.onb-skip:active { background: var(--paper-2); }

/* ---------- viewport + horizontal slide track ---------- */
.onb-viewport {
  flex: 1 1 auto;
  overflow: hidden;
  position: relative;
}
.onb-track {
  display: flex;
  height: 100%;
  width: 100%;
  transform: translateX(0);
  transition: transform 360ms var(--ease-out);
  will-change: transform;
}
.onb-slide {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.onb-slide-inner {
  padding: 20px 22px 28px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  max-width: var(--max-w);
  margin: 0 auto;
}
/* Opt-in vertical centering for slides whose content is short enough
   to leave dead space below — keeps them from looking top-heavy.
   Uses auto margins (not justify-content) so that when a slide's
   content is taller than the viewport the margins collapse to 0 and
   it scrolls normally from the top instead of being clipped. Slides
   whose content grows at runtime (store setup, first item) leave this
   off so the layout doesn't jump as rows appear. */
.onb-slide--center {
  display: flex;
  flex-direction: column;
}
.onb-slide--center .onb-slide-inner {
  margin-top: auto;
  margin-bottom: auto;
  flex-shrink: 0;
}
.onb-title {
  font-family: var(--font-display);
  font-weight: var(--w-bold);
  font-size: 26px;
  line-height: 1.15;
  color: var(--ink);
  letter-spacing: var(--track-tight);
  margin: 4px 0 0;
}
.onb-sub {
  font-family: var(--font-sans);
  font-size: var(--text-md);
  color: var(--muted);
  line-height: var(--lh-normal);
  margin: 0 0 4px;
}

/* ---------- step 1: language list ---------- */
.onb-lang-list {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--rule);
  margin-top: 4px;
}
.onb-lang-list .sheet-row {
  padding-left: 4px;
  padding-right: 4px;
}

/* ---------- step 2: how-it-works (integrated app preview) ----------
   A single rounded "preview card" that looks like a stylized slice of
   the app: an add bar at the top types "milk" one character at a time,
   and below it the sorted-list-with-category-headers reveals the new
   item landing inside the Dairy section with a soft tint + slide-in.
   The whole thing reads as a product screenshot rather than a generic
   tutorial diagram. Reveal classes:
     .is-howto-typed  → caret hides
     .is-howto-landed → milk item slides into Dairy + section tints */
.onb-howto-eyebrow {
  align-self: center;
}
.onb-howto-title-main {
  text-align: center;
  font-size: 30px;
}
.onb-howto-sub {
  text-align: center;
  max-width: 320px;
  margin: 0 auto;
}
.onb-howto-preview {
  width: 100%;
  max-width: 320px;
  margin: 14px auto 0;
  background: var(--paper);
  border-radius: 18px;
  border: 1px solid color-mix(in oklch, var(--ink) 7%, transparent);
  box-shadow:
    0 1px 0 color-mix(in oklch, var(--paper) 80%, transparent) inset,
    0 14px 36px rgba(0, 0, 0, 0.12),
    0 2px 6px rgba(0, 0, 0, 0.05);
  overflow: hidden;
  animation: onb-howto-preview-rise 520ms var(--ease-out);
}
@keyframes onb-howto-preview-rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
/* Fake add bar — gradient header strip with a tomato + button and a
   pseudo-input that shows the typed text + blinking caret. */
.onb-howto-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  background: linear-gradient(
    to bottom,
    color-mix(in oklch, var(--paper) 96%, var(--paper-2)),
    var(--paper-2)
  );
  border-bottom: 1px solid var(--rule);
}
.onb-howto-bar-icon {
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--tomato);
  color: white;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 2px 6px color-mix(in oklch, var(--tomato) 40%, transparent);
}
.onb-howto-bar-icon svg { width: 16px; height: 16px; stroke-width: 2; }
.onb-howto-bar-input {
  flex: 1;
  display: inline-flex;
  align-items: baseline;
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: var(--w-medium);
  color: var(--ink);
  min-height: 22px;
  letter-spacing: 0.01em;
}
.onb-howto-typed {
  white-space: pre;
}
.onb-howto-caret {
  display: inline-block;
  width: 2px;
  height: 16px;
  background: var(--tomato);
  margin-left: 1px;
  vertical-align: -3px;
  animation: onb-caret-blink 1s steps(1, end) infinite;
}
@keyframes onb-caret-blink {
  0%, 50% { opacity: 1; }
  51%, 100% { opacity: 0; }
}
.onb-slide[data-step="2"].is-howto-typed .onb-howto-caret {
  animation: none;
  opacity: 0;
}
/* Sorted list. Each cat is a row with a colored left-accent strip
   and a small emoji + uppercased name. The Dairy row also contains
   a hidden "landed" item that animates in once the type completes. */
.onb-howto-list {
  display: flex;
  flex-direction: column;
}
.onb-howto-cat {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  padding: 11px 14px;
  border-bottom: 1px solid var(--rule);
  position: relative;
  background: var(--paper);
  transition: background 380ms var(--ease-out);
}
.onb-howto-cat:last-child { border-bottom: 0; }
.onb-howto-cat::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--cat, var(--rule-strong));
}
.onb-howto-cat-em {
  font-size: 18px;
  line-height: 1;
}
.onb-howto-cat-name {
  font-family: var(--font-sans);
  font-weight: var(--w-semi);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
.onb-howto-cat--target {
  /* Slightly raised baseline so the landing tint reads as a deliberate
     spotlight rather than a defect. */
  z-index: 1;
}
.onb-slide[data-step="2"].is-howto-landed .onb-howto-cat--target {
  background: color-mix(in oklch, var(--cat) 14%, var(--paper));
}
.onb-howto-landed {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 4px 0 0 0;
  opacity: 0;
  transform: translateY(-10px);
  transition: opacity 360ms var(--ease-out), transform 360ms var(--ease-out);
}
.onb-slide[data-step="2"].is-howto-landed .onb-howto-landed {
  opacity: 1;
  transform: none;
}
.onb-howto-landed-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--cat, var(--ink));
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--cat, var(--ink)) 22%, transparent);
}
.onb-howto-landed-name {
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: var(--w-medium);
  color: var(--ink);
}
@media (prefers-reduced-motion: reduce) {
  .onb-howto-preview { animation: none; }
  .onb-howto-caret { animation: none; opacity: 0; }
  .onb-howto-landed { opacity: 1 !important; transform: none !important; transition: none; }
}

/* Tomato-tinted small-caps eyebrow used above section titles
   (still used by .onb-howto-eyebrow on the How-it-works slide). */
.onb-eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: var(--w-bold);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--tomato);
  margin: 0 0 -2px;
}

/* ---------- add-to-home-screen sheet ----------
   A home-screen icon preview (the real app icon, lifted on a soft
   shadow so it reads as a tile) plus one card per platform with the
   two taps it takes to install. Purely instructional — no actions. */
.onb-install-preview {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 4px 0;
}
.onb-install-icon {
  width: 64px;
  height: 64px;
  border-radius: 14px;
  overflow: hidden;
  background: var(--paper);
  box-shadow:
    0 10px 22px rgba(0, 0, 0, 0.18),
    0 0 0 1px color-mix(in oklch, var(--ink) 6%, transparent);
}
.onb-install-icon img {
  width: 100%;
  height: 100%;
  display: block;
}
.onb-install-icon-label {
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  color: var(--muted);
}
.onb-install-platforms {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.onb-install-platform {
  border: 1px solid var(--rule);
  border-radius: 14px;
  background: var(--paper);
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  box-shadow: var(--shadow-card, 0 1px 2px rgba(0, 0, 0, 0.04));
}
.onb-install-os {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: var(--w-semi);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
}
.onb-install-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.onb-install-list li {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--ink);
  line-height: var(--lh-normal);
}
.onb-install-step-ic {
  width: 30px;
  height: 30px;
  border-radius: 8px;
  background: var(--paper-2);
  color: var(--tomato);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.onb-install-step-ic svg { width: 16px; height: 16px; }
.onb-install-hint { margin: 2px 0 0; }

/* ---------- footer ---------- */
.onb-footer {
  display: flex;
  gap: 10px;
  padding: 12px 16px calc(20px + var(--safe-bottom));
  border-top: 1px solid var(--rule);
  background: var(--paper);
  flex-shrink: 0;
}
.onb-footer .ghost-btn,
.onb-footer .primary-btn {
  flex: 1;
  height: 48px;
  font-size: var(--text-md);
}

/* =============== Community layouts =============== */

/* CTA shown at the top of the store-edit sheet when creating a new store. */
.community-cta {
  display: flex;
  align-items: center;
  gap: 12px;
  width: calc(100% - 36px);
  margin: 4px 18px 14px;
  padding: 12px 14px;
  background: color-mix(in oklch, var(--berry) 8%, var(--paper));
  border: 1px solid color-mix(in oklch, var(--berry) 22%, var(--rule));
  border-radius: var(--r-3);
  text-align: left;
  cursor: pointer;
  color: var(--ink);
  font-family: inherit;
  transition: background var(--dur-quick) var(--ease-out);
}
.community-cta:active { background: color-mix(in oklch, var(--berry) 14%, var(--paper)); }
.community-cta-icon {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in oklch, var(--berry) 18%, var(--paper));
  color: var(--berry);
  flex-shrink: 0;
}
.community-cta-icon svg { width: 18px; height: 18px; }
.community-cta-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.community-cta-title {
  font-family: var(--font-sans);
  font-weight: var(--w-semi);
  font-size: var(--text-md);
  color: var(--ink);
}
.community-cta-sub {
  font-family: var(--font-sans);
  font-weight: var(--w-regular);
  font-size: var(--text-xs);
  color: var(--muted);
  line-height: 1.35;
}
.community-cta-chevron {
  width: 18px;
  height: 18px;
  color: var(--muted);
  flex-shrink: 0;
}

/* Inline status text under the store name when the store is from / has been
   published to the community directory. */
.community-status {
  margin: -4px 18px 12px;
  padding: 0;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: var(--w-medium);
  color: var(--olive);
}

/* Search sheet — full-width search box + results stack. */
.community-search-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 44px;
  margin: 0 18px 14px;
  padding: 0 12px 0 14px;
  background: var(--paper-2);
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
}
.community-search-bar .search-bar-icon {
  width: 16px;
  height: 16px;
}
.community-search-bar .search-input {
  font-size: var(--text-md);
}

.community-results {
  display: flex;
  flex-direction: column;
}
.community-result {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 18px;
  border-top: 1px solid var(--rule);
  background: transparent;
  border-left: 0;
  border-right: 0;
  border-bottom: 0;
  cursor: pointer;
  text-align: left;
  width: 100%;
  min-height: var(--tap);
  transition: background var(--dur-quick) var(--ease-out);
  font-family: inherit;
  color: var(--ink);
}
.community-result:active { background: var(--paper-2); }
.community-result:last-child { border-bottom: 1px solid var(--rule); }
.community-result-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.community-result-name {
  font-family: var(--font-sans);
  font-weight: var(--w-semi);
  font-size: var(--text-md);
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.community-result-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  color: var(--muted);
}
.community-result-city {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 60%;
}
.community-result-sep { color: var(--muted-2); }
.community-result-uses { color: var(--muted); }
.community-result-chevron {
  width: 18px;
  height: 18px;
  color: var(--muted-2);
  flex-shrink: 0;
}

.community-empty,
.community-loading,
.community-error {
  margin: 14px 18px;
  text-align: center;
}
.community-error { color: var(--tomato); }

/* Publish preview rows — label/value table laid out inline. */
.community-publish-preview {
  margin: 0 18px 14px;
  border: 1px solid var(--rule);
  border-radius: var(--r-3);
  overflow: hidden;
}
.community-publish-row {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--rule);
}
.community-publish-row:last-child { border-bottom: 0; }
.community-publish-label {
  flex: 0 0 110px;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: var(--w-medium);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.community-publish-value {
  flex: 1;
  font-family: var(--font-sans);
  font-size: var(--text-md);
  color: var(--ink);
  word-break: break-word;
}

/* ---------- Community screen — borrows the library subtab/panel structure */
.screen-community .sheet-sub {
  padding: 8px 18px 14px;
  margin: 0;
}
.screen-community .community-search-bar {
  margin: 0 18px 14px;
}

/* ---------- Address row — input + 'Find on map' inline button */
.address-row {
  display: flex;
  gap: 8px;
  align-items: stretch;
}
.address-row input {
  flex: 1;
  min-width: 0;
}
.address-find-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0 12px;
  background: var(--paper-2);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  color: var(--ink);
  font-family: var(--font-sans);
  font-weight: var(--w-semi);
  font-size: var(--text-sm);
  cursor: pointer;
  flex-shrink: 0;
  transition: background var(--dur-quick) var(--ease-out);
}
.address-find-btn:active { background: var(--paper-3); }
.address-find-btn svg {
  width: 14px;
  height: 14px;
  color: var(--berry);
}

/* ---------- Find-on-map sheet ---------- */
.places-locate-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  width: calc(100% - 36px);
  margin: 0 18px 12px;
  padding: 10px 14px;
  background: color-mix(in oklch, var(--berry) 6%, var(--paper));
  border: 1px dashed color-mix(in oklch, var(--berry) 28%, var(--rule));
  border-radius: var(--r-2);
  color: var(--ink);
  font-family: var(--font-sans);
  font-weight: var(--w-medium);
  font-size: var(--text-sm);
  cursor: pointer;
  transition: background var(--dur-quick) var(--ease-out);
}
.places-locate-btn:active { background: color-mix(in oklch, var(--berry) 12%, var(--paper)); }
.places-locate-btn:disabled { opacity: 0.6; cursor: progress; }
.places-locate-btn svg { width: 14px; height: 14px; color: var(--berry); flex-shrink: 0; }
.places-locate-status {
  margin: -8px 18px 12px;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  color: var(--muted);
}
.places-attribution {
  margin: 14px 18px 4px;
  padding: 0;
  font-family: var(--font-sans);
  font-size: 11px;
  color: var(--muted-2);
  text-align: right;
}

/* ---------- Inline "+ Add store" button anchored to the right end of the
   community search bar. Mirrors the list screen's add-btn placement so the
   "add" affordance lives in the same spot across screens. */
.community-search-bar.has-add-btn {
  position: relative;
  padding-right: 4px;
}
.community-search-bar.has-add-btn .search-input {
  padding-right: 44px;
}
.community-add-btn {
  position: absolute;
  right: 4px;
  top: 50%;
  transform: translateY(-50%);
  width: 36px;
  height: 36px;
  background: var(--tomato);
  color: #fff;
  border: 0;
  border-radius: var(--r-pill);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex-shrink: 0;
  transition: background var(--dur-quick) var(--ease-out);
}
.community-add-btn:active { background: var(--tomato-deep); }
