/* Mind the Milk.
   Light mode is the DEFAULT, not an oversight. Piepenbrock et al. measured
   legibility under glance-like reading, which is exactly how a shopping list is
   read, and light-on-dark under dark ambient light was the worst condition
   tested. Dark mode ships because people want it, not as an accessibility win.

   The brand teal LIFTS in dark mode rather than staying put: #0F766E fails
   contrast on a near-black surface, landing around 2:1. Saturated mid-tones
   visually vibrate on dark, so the fix is a lighter tone of the same hue. */
:root {
  --brand: #0F766E;
  --brand-ink: #0B5C55;
  --brand-wash: #E6F4F2;
  --on-brand: #ffffff;
  --ink: #111827;
  --ink-2: #374151;
  --muted: #6B7280;
  --line: #E5E7EB;
  --line-soft: #F3F4F6;
  --bg: #ffffff;
  --surface: #F9FAFB;
  --pending: #9CA3AF;
  --danger: #B91C1C;
  --row: 60px;
}
/* THE SAME TOKENS, TWICE, AND THAT IS DELIBERATE.

   The first block is "the machine says dark and the person has not overridden
   it". The second is "the person chose dark". CSS has no way to name one set of
   declarations and apply it from two selectors without a preprocessor, and this
   project has no build step and is not getting one to save fifteen lines.

   `:not([data-theme="light"])` is what makes the override work: choosing light on
   a machine that is dark has to beat the media query, and it does, because the
   attribute is absent only when the choice is "follow the system". See theme.js
   for why "system" removes the attribute rather than setting it to a value. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --brand: #2DD4BF; --brand-ink: #5EEAD4; --brand-wash: #12312E; --on-brand: #062B27;
    --ink: #F3F4F6; --ink-2: #D1D5DB; --muted: #9CA3AF;
    --line: #1F2A37; --line-soft: #161F2B;
    --bg: #111827; --surface: #0B1220; --pending: #6B7280; --danger: #F87171;
  }
}
:root[data-theme="dark"] {
    --brand: #2DD4BF; --brand-ink: #5EEAD4; --brand-wash: #12312E; --on-brand: #062B27;
    --ink: #F3F4F6; --ink-2: #D1D5DB; --muted: #9CA3AF;
    --line: #1F2A37; --line-soft: #161F2B;
    --bg: #111827; --surface: #0B1220; --pending: #6B7280; --danger: #F87171;
}

/* Kills Chrome Android's pull-to-refresh, which collides with a list you scroll
   constantly. Not Baseline, so nothing may DEPEND on it. Suppressing it means we
   owe the user our own refresh, which is the tap on the sync line. */
html { overscroll-behavior: none; }

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 16px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  /* svh, not dvh: MDN cautions that dvh resizes content mid-scroll. Installed as
     a PWA there is no retracting toolbar, so they are equal anyway. */
  min-height: 100svh;
}

/* PHONE: one column, add bar last so it sits at the bottom in thumb reach.
   Responsive behaviour is a design decision, not a default. This one was
   specified rather than guessed. */
.app {
  display: grid;
  grid-template-areas: "head" "lists" "shop" "chips" "list" "strip" "sugg" "add";
  grid-template-rows: auto auto auto auto minmax(0, 1fr) auto auto auto;
  height: 100svh;
  max-width: 34rem;
  margin: 0 auto;
  position: relative;
}
/* A GRID ITEM DEFAULTS TO min-width: auto, WHICH MEANS "as wide as my content
   needs". So one horizontally scrolling child with enough in it drags the whole
   column wider than the viewport, and the entire app scrolls sideways.

   Found 2026-08-08 by driving the app in a real browser: the Recently bought
   strip did it as soon as there were six or seven purchases, which is week one
   of ordinary use. Nothing errored, nothing logged, and every earlier test had
   an empty strip because nothing had been bought yet.

   Applied to every child rather than to the strip alone, because the same trap
   is waiting for the next child that scrolls. */
.app > * { min-width: 0; }

.hd { grid-area: head; }
.lists { grid-area: lists; }
.shopbar { grid-area: shop; }
.chips { grid-area: chips; }
.scroll { grid-area: list; }
.strip { grid-area: strip; }
.sugg { grid-area: sugg; }
.add { grid-area: add; }

/* LAPTOP: two panes. Lists become a sidebar, and the add bar moves to the TOP,
   because bottom anchoring exists for thumbs and a laptop does not have one. */
@media (min-width: 56.25rem) {
  .app {
    max-width: 64rem;
    grid-template-columns: 15rem minmax(0, 1fr);
    grid-template-areas:
      "lists head"
      "lists add"
      "lists sugg"
      "lists shop"
      "lists chips"
      "lists list"
      "lists strip";
    grid-template-rows: auto auto auto auto auto minmax(0, 1fr) auto;
    border-inline: 1px solid var(--line);
  }
  .add { border-top: 0; border-bottom: 1px solid var(--line); padding-bottom: 11px; }
  .sugg { border-top: 0; border-bottom: 1px solid var(--line); }
  .toast { bottom: 24px; left: auto; right: 24px; width: min(24rem, 40vw); }
}

/* ---- header ---- */
.hd { padding: calc(10px + env(safe-area-inset-top, 0px)) 16px 10px; flex: none; }
.title { display: flex; align-items: center; gap: 10px; }
.mark { width: 24px; height: 24px; color: var(--brand); flex: none; }
h1 { margin: 0; font-size: 1.45rem; font-weight: 700; letter-spacing: -.02em; }
.sync {
  margin: 2px 0 0; font-size: .74rem; color: var(--muted);
  background: none; border: 0; padding: 4px 0; cursor: pointer; text-align: left;
}
.sync.stale { color: var(--danger); }

/* ---- lists: chip row on a phone, sidebar on a laptop ---- */
.lists { display: flex; gap: 7px; padding: 0 16px 10px; overflow-x: auto; scrollbar-width: none; }
.lists::-webkit-scrollbar { display: none; }
.lists .lb { display: none; }
.listbtn {
  display: flex; align-items: center; gap: 8px; flex: none;
  font: inherit; font-size: .88rem; font-weight: 560; padding: 8px 14px;
  border-radius: 999px; border: 1px solid var(--line);
  background: var(--bg); color: var(--ink-2); cursor: pointer; min-height: 38px;
}
.listbtn[aria-current="true"] {
  background: var(--brand); border-color: var(--brand); color: var(--on-brand); font-weight: 650;
}
.listbtn .count {
  font-size: .72rem; font-weight: 700; opacity: .75;
  background: rgba(0,0,0,.08); border-radius: 999px; padding: 1px 6px;
}
.listbtn[aria-current="true"] .count { background: rgba(255,255,255,.22); }
.listbtn.add { color: var(--muted); border-style: dashed; }

@media (min-width: 56.25rem) {
  .lists {
    flex-direction: column; gap: 3px; overflow-x: visible; overflow-y: auto;
    border-right: 1px solid var(--line); background: var(--surface);
    padding: calc(14px + env(safe-area-inset-top, 0px)) 10px 14px;
  }
  .lists .lb {
    display: block; font-size: .68rem; font-weight: 680; letter-spacing: .08em;
    text-transform: uppercase; color: var(--muted); padding: 0 10px 8px;
  }
  .listbtn {
    width: 100%; border: 0; border-radius: 9px; background: none;
    justify-content: space-between; padding: 10px 12px; min-height: 42px;
  }
  .listbtn:hover { background: var(--bg); }
  .listbtn[aria-current="true"] { background: var(--brand-wash); color: var(--brand-ink); }
  .listbtn[aria-current="true"] .count { background: rgba(0,0,0,.08); }
  .listbtn.add { border: 0; margin-top: 6px; }
}
@media (prefers-color-scheme: dark) and (min-width: 56.25rem) {
  .listbtn[aria-current="true"] { color: var(--brand); }
  .listbtn .count, .listbtn[aria-current="true"] .count { background: rgba(255,255,255,.1); }
}

/* ---- store filter: a VIEW over one list, never separate lists ---- */
.chips {
  display: flex; gap: 7px; padding: 0 16px 10px; flex: none;
  overflow-x: auto; scrollbar-width: none;
}
.chips::-webkit-scrollbar { display: none; }
.chip {
  font: inherit; font-size: .82rem; padding: 8px 14px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--bg); color: var(--ink-2);
  white-space: nowrap; cursor: pointer; min-height: 36px;
}
.chip[aria-selected="true"] {
  background: var(--brand); border-color: var(--brand); color: var(--on-brand); font-weight: 600;
}

/* ---- the list ---- */
.scroll { flex: 1; overflow-y: auto; overscroll-behavior-y: contain; padding-bottom: 8px; }

.cat {
  display: flex; align-items: center; gap: 8px;
  font-size: .72rem; font-weight: 680; letter-spacing: .07em; text-transform: uppercase;
  color: var(--muted); padding: 12px 16px 4px;
}

/* The whole row is the tick target, because it is the highest-frequency action
   in the app and the design case is a gloved thumb on a moving trolley.
   60px is well past every platform minimum, deliberately. */
.row { display: flex; align-items: center; padding-right: 10px; position: relative; }
.row + .row::before {
  content: ""; position: absolute; top: 0; left: 56px; right: 0; height: 1px; background: var(--line);
}
/* THE TICK ZONE. It used to be the whole row, and that was deliberate: highest
   frequency action, biggest target, chosen for a gloved thumb pushing a trolley.
   On 2026-08-08 tapping the name became "open the detail sheet" (Paul), so the
   tick shrank to the checkbox and the icon together.

   It is still a real target: full row height (60px) by about 88px, which clears
   the 44px minimum comfortably. But it IS smaller than it was, and it is the one
   change of that day that could feel worse in an actual shop. Watch it in use.
   Reverting means putting the name back inside this button. */
.tickzone {
  display: flex; align-items: center; gap: 12px; flex: none;
  padding: 0 8px 0 16px; min-height: var(--row);
  background: none; border: 0; text-align: left; font: inherit; color: inherit; cursor: pointer;
}
.more { font-weight: 500; text-transform: none; letter-spacing: 0; opacity: .8; }
.sg.on { background: var(--brand-wash); }
.box {
  width: 26px; height: 26px; border-radius: 7px; border: 2px solid #9CA3AF;
  flex: none; display: grid; place-items: center; color: var(--on-brand);
}
.box svg { width: 17px; height: 17px; opacity: 0; }
.ic { width: 23px; height: 23px; flex: none; color: var(--muted); }
/* The name is now a button of its own, opening the detail sheet. It keeps the
   remaining width so it is still a large target, just for a different action. */
.nm {
  font-size: 1.06rem; font-weight: 600; letter-spacing: -.01em; flex: 1; min-width: 0;
  background: none; border: 0; padding: 8px 4px; min-height: var(--row);
  font-family: inherit; color: inherit; text-align: left; cursor: pointer;
  display: flex; flex-direction: column; justify-content: center; gap: 2px;
}
.nmtop { display: block; }
.meta { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.qty {
  display: inline-block; margin-left: 8px; font-size: .78rem; font-weight: 600;
  color: var(--brand-ink); background: var(--brand-wash);
  border-radius: 5px; padding: 2px 7px; vertical-align: 1.5px;
}
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .qty { color: var(--brand); } }
:root[data-theme="dark"] .qty { color: var(--brand); }
.note { display: block; font-size: .78rem; font-weight: 500; color: var(--muted); }
/* Grey, never amber, never red: not yet synced is not an error. */
.pending { width: 9px; height: 9px; border-radius: 50%; border: 2px solid var(--pending); flex: none; }

.row[data-state="in_trolley"] .box { border-color: var(--brand); background: var(--brand-wash); }
.row[data-state="in_trolley"] .nm { color: var(--ink-2); }
.row[data-state="done"] .box { background: var(--brand); border-color: var(--brand); }
.row[data-state="done"] .box svg { opacity: 1; }
.row[data-state="done"] .nm {
  color: var(--muted); text-decoration: line-through; text-decoration-thickness: 1.6px; font-weight: 500;
}
.row[data-state="done"] .ic { opacity: .45; }

/* Every swipe needs a tap alternative: WCAG 2.5.1 is Level A and names
   path-based gestures explicitly. So the delete is a real button, not a swipe. */
.del {
  background: none; border: 0; padding: 8px; margin: -8px -6px -8px 0; color: var(--muted);
  cursor: pointer; border-radius: 8px; min-width: 40px; min-height: 40px; display: grid; place-items: center;
}
.del svg { width: 17px; height: 17px; }
.del:hover, .del:focus-visible { color: var(--danger); }

/* ---- empty state: a capture surface, never a poster ---- */
.empty { padding: 30px 16px 6px; text-align: center; }
.empty .mark { width: 52px; height: 52px; opacity: .85; margin: 0 auto 12px; display: block; }
.empty h2 { margin: 0 0 5px; font-size: 1.1rem; font-weight: 660; }
.empty p { margin: 0; font-size: .86rem; color: var(--muted); }
.tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; padding: 18px 16px 0; }
.tile {
  border: 1px solid var(--line); border-radius: 13px; padding: 13px 6px 11px;
  background: var(--bg); color: var(--ink-2); font: inherit; font-size: .8rem; font-weight: 560;
  cursor: pointer; min-height: 72px;
}
.tile .ic { width: 27px; height: 27px; color: var(--brand); opacity: .9; margin: 0 auto 5px; display: block; }

[hidden] { display: none !important; }

/* ---- recently bought: purchase history, wearing a useful hat ---- */
.strip { border-top: 1px solid var(--line); padding: 10px 0 9px; flex: none; background: var(--surface); }
.strip .lb {
  font-size: .72rem; font-weight: 680; letter-spacing: .07em; text-transform: uppercase;
  color: var(--muted); padding: 0 16px 8px;
}
.pills { display: flex; gap: 8px; padding: 0 16px; overflow-x: auto; scrollbar-width: none; }
.pills::-webkit-scrollbar { display: none; }
.pill {
  display: flex; align-items: center; gap: 7px; flex: none;
  border: 1px solid var(--line); background: var(--bg); border-radius: 999px;
  padding: 8px 13px 8px 10px; font: inherit; font-size: .86rem; font-weight: 560;
  color: var(--ink-2); white-space: nowrap; cursor: pointer; min-height: 40px;
}
.pill .ic { width: 18px; height: 18px; }
.pill .plus { color: var(--brand); font-weight: 700; }
.pill .why { font-size: .7rem; color: var(--muted); }

/* ---- type-ahead ---- */
.sugg { flex: none; border-top: 1px solid var(--line); background: var(--bg); max-height: 40vh; overflow-y: auto; }
.sg {
  display: flex; align-items: center; gap: 11px; width: 100%; min-height: 52px;
  padding: 0 16px; background: none; border: 0; border-bottom: 1px solid var(--line-soft);
  font: inherit; font-size: 1rem; color: var(--ink-2); text-align: left; cursor: pointer;
}
.sg .ic { width: 20px; height: 20px; }
/* `.hit-t`, not `.hit`. The markup was renamed to avoid colliding with the row
   tick target and this rule was not, so the matched substring in a suggestion
   rendered as plain text and nobody noticed. Found 2026-08-08 by a test that
   compares the classes the markup uses against the ones the stylesheet defines,
   which is the same check that would have caught the drawer bug in minutes. */
.sg .hit-t { font-weight: 700; color: var(--ink); }
.sg .why { margin-left: auto; font-size: .72rem; color: var(--muted); }

/* ---- add bar: full width in the lower third, never a corner button ---- */
.add {
  flex: none; display: flex; gap: 9px; align-items: center;
  border-top: 1px solid var(--line); background: var(--bg);
  padding: 11px 14px calc(11px + env(safe-area-inset-bottom, 0px));
}
.fieldwrap { flex: 1; min-width: 0; }
.field {
  width: 100%; height: 52px; border-radius: 14px;
  background: var(--surface); border: 1px solid var(--line); color: var(--ink);
  padding: 0 14px; font: inherit; font-size: 1.03rem;
}
.field::placeholder { color: var(--muted); }
.field:focus { outline: 2px solid var(--brand); outline-offset: -1px; background: var(--bg); }
.btn {
  width: 52px; height: 52px; border-radius: 14px; border: 0; flex: none;
  display: grid; place-items: center; cursor: pointer;
  background: var(--brand); color: var(--on-brand);
}
.btn svg { width: 24px; height: 24px; }
.btn.mic { background: var(--surface); border: 1px solid var(--line); color: var(--muted); }
.btn:disabled { opacity: .45; cursor: default; }

/* ---- toast: undo beats confirmation ---- */
.toast {
  position: absolute; left: 16px; right: 16px; bottom: calc(84px + env(safe-area-inset-bottom, 0px));
  background: var(--ink); color: var(--bg); border-radius: 12px;
  padding: 12px 14px; display: flex; align-items: center; gap: 12px;
  font-size: .9rem; box-shadow: 0 8px 24px rgba(0,0,0,.22); z-index: 5;
}
/* Without flex the Undo button sits against the text rather than at the end. */
.toast .msg { flex: 1; min-width: 0; }
.toast button {
  margin-left: auto; background: none; border: 0; color: var(--brand);
  font: inherit; font-weight: 700; cursor: pointer; padding: 6px 4px;
}
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .toast { background: #1F2A37; color: var(--ink); } }
:root[data-theme="dark"] .toast { background: #1F2A37; color: var(--ink); }

/* Reduced motion: replace the movement, keep the meaning. Never delete the
   feedback, or a tick stops confirming anything. */
@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* ==========================================================================
   Added 2026-08-08: the shop bar, the occasion and deadline chips, the
   Everything else fold, and the one sheet. Responsive behaviour specified by
   Paul rather than guessed at, per the rule in CLAUDE.md.
   ========================================================================== */

/* ---- the shop bar: where you are, and what your ticks are teaching ----
   Sticky under the heading on BOTH layouts. It is not decoration: a person must
   never have to guess which shop is about to learn something from them. */
.shopbar { padding: 0 16px 10px; flex: none; }
.shopset {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; min-height: 42px; padding: 9px 14px; border-radius: 10px;
  border: 1px dashed var(--line); background: var(--surface);
  font: inherit; font-size: .88rem; color: var(--muted); cursor: pointer; text-align: left;
}
.shopset.on {
  border-style: solid; border-color: var(--brand); background: var(--brand-wash);
  color: var(--brand-ink);
}
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .shopset.on { color: var(--brand); } }
:root[data-theme="dark"] .shopset.on { color: var(--brand); }
.shopset .lbl { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.shopset .lbl strong { font-weight: 700; }
.shopset .act { flex: none; font-size: .76rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; opacity: .8; }

/* ---- occasion and deadline, on a row ----
   An occasion is a filter, never a list. It reads as a quiet chip beside the
   name so the row still scans as one line at arm's length. */
.occ {
  font-size: .72rem; font-weight: 650; letter-spacing: .01em; color: var(--ink-2);
  background: var(--line-soft); border-radius: 5px; padding: 2px 7px;
}
.due {
  font-size: .74rem; font-weight: 600; color: var(--muted);
}
/* Amber for soon and red for late, and nothing at all otherwise. A date that is
   simply a date must not shout, or the ones that matter stop registering. */
.due.soon { color: #B45309; }
.due.late { color: var(--danger); font-weight: 700; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .due.soon { color: #FBBF24; } }
:root[data-theme="dark"] .due.soon { color: #FBBF24; }
.row[data-state="done"] .occ, .row[data-state="done"] .due { opacity: .5; }

/* ---- Everything else ----
   Collapsed with a count on a phone, open on a laptop where there is room.
   Collapsed rather than hidden, ALWAYS: this section is what makes the first
   purchase at a new shop possible, and a hard filter would quietly stop the app
   from ever learning where anything is sold. */
.fold {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 14px 16px 10px; margin: 0; background: none; border: 0;
  font: inherit; font-size: .72rem; font-weight: 680; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted); cursor: pointer; text-align: left;
  min-height: 44px;
}
.fold .chev { width: 14px; height: 14px; flex: none; transition: transform .15s ease; }
.fold[aria-expanded="true"] .chev { transform: rotate(90deg); }
.fold .cnt {
  font-size: .72rem; letter-spacing: 0; background: var(--line-soft);
  color: var(--ink-2); border-radius: 999px; padding: 1px 8px;
}
.elsewhere { opacity: .82; }

/* ---- the sheet: one overlay, three uses ----
   Slides from the bottom on a phone (thumb reach), centred on a laptop (no
   thumb to reach with). Identical markup; the difference is entirely here. */
.scrim {
  position: fixed; inset: 0; background: rgba(17, 24, 39, .45); z-index: 40;
  animation: fade .14s ease;
}
body.locked { overflow: hidden; }
.sheet {
  position: fixed; inset: auto 0 0 0; z-index: 50;
  display: flex; justify-content: center;
  animation: rise .18s cubic-bezier(.2, .8, .3, 1);
}
.sheetcard {
  width: 100%; max-width: 34rem; background: var(--bg);
  border-radius: 16px 16px 0 0; border: 1px solid var(--line); border-bottom: 0;
  padding: 4px 16px calc(18px + env(safe-area-inset-bottom, 0px));
  max-height: 86svh; overflow-y: auto;
}
.sheethead { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 0 6px; position: sticky; top: 0; background: var(--bg); }
.sheethead h2 { margin: 0; font-size: 1.12rem; font-weight: 680; letter-spacing: -.01em;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sheetx { background: none; border: 0; color: var(--muted); cursor: pointer;
  min-width: 44px; min-height: 44px; display: grid; place-items: center; border-radius: 8px; }
.sheetx svg { width: 18px; height: 18px; }
.sheetbody { padding: 4px 0 2px; }

.bigbtns { display: grid; gap: 8px; padding: 4px 0 2px; }
.bigbtn {
  font: inherit; font-size: 1rem; font-weight: 650; min-height: 52px; width: 100%;
  border-radius: 11px; border: 1px solid var(--brand); background: var(--brand);
  color: var(--on-brand); cursor: pointer; padding: 12px 16px;
}
.bigbtn.ghost { background: var(--bg); color: var(--ink-2); border-color: var(--line); font-weight: 600; }
.bigbtn.danger { color: var(--danger); border-color: var(--line); }
.sheetacts { display: grid; gap: 8px; padding-top: 14px; }

.fld { display: grid; gap: 5px; padding: 10px 0 2px; }
.fld > span { font-size: .72rem; font-weight: 680; letter-spacing: .07em;
  text-transform: uppercase; color: var(--muted); }
.fld input, .fld select {
  font: inherit; font-size: 1rem; min-height: 48px; width: 100%;
  padding: 10px 12px; border-radius: 10px; border: 1px solid var(--line);
  background: var(--bg); color: var(--ink);
}
.hint { font-size: .82rem; color: var(--muted); margin: 12px 0 0; line-height: 1.45; }
/* A failure the person can act on, inside a sheet they opened on purpose. Red is
   right here and wrong on the sync line: this is a button that did not work, not
   a network that will sort itself out. */
.hint.bad { color: var(--danger); font-weight: 600; }

@keyframes rise { from { transform: translateY(14px); opacity: 0; } }
@keyframes fade { from { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .sheet, .scrim { animation: none; }
  .fold .chev { transition: none; }
}

/* LAPTOP: the sheet stops being a sheet and becomes a panel. */
@media (min-width: 56.25rem) {
  .sheet { inset: 0; align-items: center; }
  .sheetcard {
    max-width: 26rem; border-radius: 14px; border-bottom: 1px solid var(--line);
    padding-bottom: 18px; max-height: 80svh;
  }
  .shopbar { padding-bottom: 12px; }
}

/* ---- the theme control: one button, three states, no menu ---- */
.themebtn {
  background: none; border: 0; padding: 0; margin-left: auto; cursor: pointer;
  color: var(--muted); border-radius: 8px;
  min-width: 40px; min-height: 40px; display: grid; place-items: center; flex: none;
}
.themebtn svg { width: 18px; height: 18px; }
.themebtn:hover, .themebtn:focus-visible { color: var(--ink-2); background: var(--surface); }
/* Only the icon for the mode currently in force is drawn. Three icons in the
   markup and two of them hidden beats swapping paths in JavaScript, because the
   sun and the moon then differ by a CSS rule rather than by a code path. */
.themebtn [data-icon] { display: none; }
.themebtn[data-mode="system"] [data-icon="system"],
.themebtn[data-mode="light"] [data-icon="light"],
.themebtn[data-mode="dark"] [data-icon="dark"] { display: block; }
.title { gap: 10px; width: 100%; }

/* Occasions already in use, as tappable chips inside the detail sheet. The field
   above them stays, for anybody typing a new one. */
.pickrow { display: flex; flex-wrap: wrap; gap: 6px; padding: 8px 0 2px; }
.chip.pick { font-size: .8rem; padding: 6px 12px; min-height: 34px; }
.chip.pick[aria-pressed="true"] {
  background: var(--brand); border-color: var(--brand); color: var(--on-brand); font-weight: 600;
}
