/* ==========================================================================
   PRV — Design V2 grocery
   ==========================================================================

   GATE B — every selector in this file is rooted at `#view-list.dv2-grocery`.

   `#view-list` is shared: Tasks, Trips and every non-grocery shopping list
   render through the same view, the same `renderShop()`, the same
   `sectionHTML()` and the same `shopRowHTML()`. app.js sets the
   `dv2-grocery` class only when `isGroceryList()` is true — which requires
   both a /grocer/i name match and a shopping-typed list — so a rule written
   here can never reach those other screens.

   Two rules for anyone editing this file:

     1. No selector may start above `#view-list.dv2-grocery`. Not `.item-row`,
        not `#shop-list`, not `body`. If a rule needs to escape that root, it
        does not belong in Design V2 grocery.
     2. Nothing goes in style.css. If a shared rule can't be beaten from here
        on specificity, stop and raise it rather than editing shared styling.

   Two documented exceptions to rule 1, both of which still carry a gate:

     · `body.dv2-groc …` — GATE B, body half. `#sheet-filter`, `#sheet-shop-item`
       and `#sheet-suggest` are siblings of `#view-list`, not descendants, so a
       selector rooted at `#view-list.dv2-grocery` physically cannot reach them.
       The aurora backdrop is here for the same reason: it has to cover the
       whole viewport, and `#view-list`'s box stops at the end of the list.
       `renderShop()` sets `dv2-groc` and `show()` clears it on leaving the list,
       so it can never strand on another screen.
     · `html[data-dv2-static="1"] #view-list.dv2-grocery …` — the deterministic
       screenshot hook. Same convention as design-v2-onboarding.css:540 and
       design-v2-app.css:767; the gate is still in the selector, and app.js
       never sets the attribute.

   Tokens come from design-v2-app.css, which declares the `--dv2-*` set on
   `#view-list.dv2-grocery` among its hosts (dark :38, light :155).

   SPECIFICITY NOTE. `#view-list.dv2-grocery` is (1,1,0), which already beats
   every class-only rule in style.css AND its `html[data-theme="…"] .section`
   dark overrides (0,2,1). That means one un-themed declaration written against
   the `--dv2-*` tokens covers BOTH themes. There is deliberately only one
   `html[data-theme]` block below, and it declares tokens rather than winning a
   specificity fight — see the note on the two grocery card surfaces.
   ========================================================================== */


/* ── Root: typography + the two list surfaces ─────────────────────────────
   The aurora backdrop lives on `body.dv2-groc`, at the bottom of this file —
   see the note there for why it cannot live on this rule.

   The shared `--dv2-g-*` glass steps were tuned for content sitting on the
   deep onboarding/dashboard aurora. The grocery aurora is much paler, and at
   `--dv2-g-09-bg` (white at .62) a row card is nearly the same value as the
   background behind it — the list stops reading as a list. These two surfaces
   therefore get their own, more opaque steps. They are the only per-theme
   block in the file. */
#view-list.dv2-grocery {
  font-family: 'Plus Jakarta Sans', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  -webkit-font-smoothing: antialiased;
}

html[data-theme="light"] #view-list.dv2-grocery {
  --dv2-groc-card-bg: rgba(255,255,255,0.78);
  --dv2-groc-card-bd: rgba(255,255,255,0.95);
  --dv2-groc-head-bg: rgba(255,255,255,0.44);
  --dv2-groc-head-bd: rgba(255,255,255,0.72);
}
html[data-theme="dark"] #view-list.dv2-grocery {
  --dv2-groc-card-bg: rgba(255,255,255,0.09);
  --dv2-groc-card-bd: rgba(255,255,255,0.15);
  --dv2-groc-head-bg: rgba(255,255,255,0.05);
  --dv2-groc-head-bd: rgba(255,255,255,0.10);
}

#view-list.dv2-grocery *,
#view-list.dv2-grocery *::before,
#view-list.dv2-grocery *::after { box-sizing: border-box; }

/* The sections animate in with `rise`; the shared `.list` top padding is tuned
   for the old flat header. */
#view-list.dv2-grocery .list { padding-top: 10px; }


/* ── Header ────────────────────────────────────────────────────────────────
   style.css paints `#view-list .topbar` (1,2,0) with a hue-derived gradient in
   both themes. `#view-list.dv2-grocery .topbar` is (1,3,0), so one rule
   replaces both. Grocery drops the per-list hue entirely — Design V2 has a
   single accent, and a green-tinted header would fight the aurora. */
#view-list.dv2-grocery .topbar {
  background: var(--dv2-g-08-bg);
  border-bottom: .75px solid var(--dv2-divider);
  -webkit-backdrop-filter: blur(28px) saturate(var(--dv2-g-sat));
  backdrop-filter: blur(28px) saturate(var(--dv2-g-sat));
  padding-bottom: 10px;
}

#view-list.dv2-grocery .topbar-row { align-items: center; }

/* `.back-btn` is forced solid navy with `!important` in style.css (a deliberate
   fix — glass chevrons vanished on pale headers). Against the aurora the glass
   pill reads fine and the navy chip is the thing that looks wrong, so this is
   the one place the file has to answer `!important` with `!important`. It is
   scoped to grocery, so the a89 fix still stands everywhere else. */
#view-list.dv2-grocery .back-btn {
  width: 40px; height: 40px;
  border-radius: 9999px;
  background: var(--dv2-g-10-bg) !important;
  border: .75px solid var(--dv2-g-10-bd) !important;
  color: var(--dv2-text-primary) !important;
  -webkit-backdrop-filter: blur(18px) saturate(var(--dv2-g-sat)) !important;
  backdrop-filter: blur(18px) saturate(var(--dv2-g-sat)) !important;
  box-shadow: none !important;
}
#view-list.dv2-grocery .back-btn svg,
#view-list.dv2-grocery .back-btn svg * {
  stroke: var(--dv2-text-primary) !important;
  color: var(--dv2-text-primary) !important;
  fill: none !important;
}

/* b232 · R9 — -.03em joins the canon at -.02em; the size and weight already
   agreed with it. */
#view-list.dv2-grocery .lp-name {
  font-size: 28px; font-weight: 800; letter-spacing: -.02em;
  color: var(--dv2-text-primary);
}
/* "0/20 done" — set by renderShop()'s grocery branch. */
#view-list.dv2-grocery #shop-sub {
  margin: 3px 0 0;
  font-size: 12px; font-weight: 500; letter-spacing: .04em;
  color: var(--dv2-text-sub);
  font-variant-numeric: tabular-nums;
}


/* ── Action toolbar ───────────────────────────────────────────────────────
   Kept as-is functionally (filter / collapse / clear ticks / about / options);
   only the pill's colour scheme moves from the list hue to Design V2 glass. */
#view-list.dv2-grocery .lp-tools {
  margin: 12px 0 0;
  padding: 5px 6px;
  border-radius: 16px;
  background: var(--dv2-g-07-bg);
  border: 1px solid var(--dv2-g-07-bd);
  -webkit-backdrop-filter: blur(20px) saturate(var(--dv2-g-sat));
  backdrop-filter: blur(20px) saturate(var(--dv2-g-sat));
  box-shadow: none;
}
#view-list.dv2-grocery .lp-tool {
  height: 40px;
  border-radius: 11px;
  color: var(--dv2-text-sub);
}
#view-list.dv2-grocery .lp-tool:hover { background: var(--dv2-g-07-bg); }
#view-list.dv2-grocery .lp-tool:active {
  transform: scale(.94);
  background: var(--dv2-g-10-bg);
}
#view-list.dv2-grocery .lp-tool[aria-pressed="true"] {
  background: var(--dv2-g-10-bg);
  border: .75px solid var(--dv2-g-10-bd);
  color: var(--dv2-accent);
}

/* Overall progress: a hairline rail under the toolbar rather than the boxed
   row style.css draws, so the header stays quiet. */
#view-list.dv2-grocery .overall {
  gap: 10px;
  margin-top: 10px;
  align-items: center;
}
#view-list.dv2-grocery .overall .bar {
  height: 4px;
  border-radius: 9999px;
  background: var(--dv2-progress-bg);
  overflow: hidden;
}
#view-list.dv2-grocery .overall .bar > span {
  display: block; height: 100%;
  border-radius: 9999px;
  background: var(--dv2-progress-fill);
  transition: width .45s ease;
}
#view-list.dv2-grocery .overall-txt {
  font-size: 11px; font-weight: 600; letter-spacing: .04em;
  color: var(--dv2-text-muted);
}
/* renderShop() blanks `#shop-txt` when nothing is flagged but never re-hides
   `.shop-progress`, so an empty grocery list draws an empty rail under the
   toolbar. Styling the rail made that visible, so hide it while the empty
   state is on screen. Shared behaviour is untouched — this is CSS, and it is
   gated. */
#view-list.dv2-grocery:has(.dv2-gempty) .overall { display: none; }


/* ── Empty state ──────────────────────────────────────────────────────────
   Markup from emptyListHtml()'s grocery branch. Approved decision G7 keeps the
   existing 12-tile section grid below the CTAs rather than deleting it, so the
   grid's own "Pick a section…" intro is redundant — the divider says it. */
#view-list.dv2-grocery .dv2-gempty {
  display: flex; flex-direction: column; align-items: center;
  text-align: center;
  padding: 44px 20px 8px;
  animation: dv2FadeUp .5s ease both;
}

#view-list.dv2-grocery .dv2-gempty-orb {
  display: grid; place-items: center;
  width: 76px; height: 76px;
  margin-bottom: 20px;
  border-radius: 9999px;
  background: var(--dv2-g-09-bg);
  border: 1px solid var(--dv2-g-16-bd);
  -webkit-backdrop-filter: blur(20px) saturate(var(--dv2-g-sat));
  backdrop-filter: blur(20px) saturate(var(--dv2-g-sat));
  box-shadow: var(--dv2-g-shadow);
  color: var(--dv2-text-primary);
}

#view-list.dv2-grocery .dv2-gempty-title {
  margin: 0;
  font-size: 22px; font-weight: 800; letter-spacing: -.02em;
  color: var(--dv2-text-primary);
}
#view-list.dv2-grocery .dv2-gempty-sub {
  margin: 8px 0 0;
  max-width: 30ch;
  font-size: 14px; line-height: 1.5;
  color: var(--dv2-text-sub);
}

#view-list.dv2-grocery .dv2-gempty-cta {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  margin-top: 26px;
  padding: 15px 30px;
  border: 0; border-radius: 9999px;
  background: var(--dv2-btn-primary);
  box-shadow: var(--dv2-btn-shadow);
  color: #fff;
  font-family: inherit;
  font-size: 16px; font-weight: 700; letter-spacing: -.01em;
  cursor: pointer;
  transition: transform .12s ease;
}
#view-list.dv2-grocery .dv2-gempty-cta:active { transform: scale(.96); }

#view-list.dv2-grocery .dv2-gempty-alt {
  margin-top: 12px;
  padding: 10px 18px;
  border: 0; background: none;
  color: var(--dv2-link);
  font-family: inherit;
  font-size: 14px; font-weight: 600;
  cursor: pointer;
}

#view-list.dv2-grocery .dv2-gempty-or {
  display: flex; align-items: center; gap: 12px;
  width: 100%;
  margin: 28px 0 4px;
}
#view-list.dv2-grocery .dv2-gempty-or::before,
#view-list.dv2-grocery .dv2-gempty-or::after {
  content: ""; flex: 1; height: 1px;
  background: var(--dv2-divider);
}
#view-list.dv2-grocery .dv2-gempty-or span {
  font-size: 11px; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--dv2-text-muted);
}

/* G7 — the quick-add grid stays, restyled as glass tiles. */
#view-list.dv2-grocery .gsec-intro { display: none; }
#view-list.dv2-grocery .gsec-grid { gap: 10px; padding-bottom: 8px; }
#view-list.dv2-grocery .gsec {
  padding: 15px 14px;
  border-radius: 16px;
  background: var(--dv2-g-07-bg);
  border: 1px solid var(--dv2-g-07-bd);
  -webkit-backdrop-filter: blur(16px) saturate(var(--dv2-g-sat));
  backdrop-filter: blur(16px) saturate(var(--dv2-g-sat));
  color: var(--dv2-text-primary);
  font-family: inherit;
  font-size: 15px; font-weight: 600;
}
#view-list.dv2-grocery .gsec:active { filter: none; transform: scale(.97); }


/* ── Category sections ────────────────────────────────────────────────────
   No markup change: Gate B sits above every `.section` in the DOM, so this is
   pure CSS. sectionHTML() has a second call site in renderTrip() — leaving it
   untouched is what keeps Trips out of this phase entirely.

   STRUCTURE. Build b121 ("one card per item", style.css:7642) already
   dissolved the section container — `.list .section` is forced transparent,
   borderless and shadowless with `!important`, `.section::before` is
   `display: none !important`, the section HEAD is its own rounded card, and
   every `.row-swipe` is its own card below it. That is also the structure the
   Design V2 grocery artefact draws (design_v2_highfidelity.css:804, a detached
   `.section-header` bar above free-standing rows), so Phase A restyles those
   two surfaces rather than fighting b121 for the old grouped card. Nothing
   here needs `!important` as a result, and b121's shared decision stands.

   The section container itself is therefore left entirely alone. */

/* The header card: b121 gives it `border-radius: 14px` and a 10px gap below;
   style.css fills it with a per-section hue band. Design V2 has one accent, so
   the band becomes glass and the category name carries the identity. */
#view-list.dv2-grocery .section-head {
  gap: 12px;
  padding: 13px 16px;
  border-radius: 16px;
  background: var(--dv2-groc-head-bg);
  border: 1px solid var(--dv2-groc-head-bd);
  -webkit-backdrop-filter: blur(20px) saturate(var(--dv2-g-sat));
  backdrop-filter: blur(20px) saturate(var(--dv2-g-sat));
}
/* style.css tints a completed section's header green. */
#view-list.dv2-grocery .section.done .section-head {
  background: var(--dv2-g-07-bg);
}

#view-list.dv2-grocery .section-head h2 {
  font-size: 15px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--dv2-text-primary);
}
#view-list.dv2-grocery .section.done .section-head h2 {
  color: var(--dv2-text-sub);
}

#view-list.dv2-grocery .section-count {
  font-size: 12px; font-weight: 600;
  color: var(--dv2-text-muted);
}
#view-list.dv2-grocery .chev { color: var(--dv2-text-muted); }
#view-list.dv2-grocery .sec-grip { color: var(--dv2-text-muted); }
#view-list.dv2-grocery .sec-grip:active { background: var(--dv2-g-10-bg); }

/* Amber "N still to buy" pill on a collapsed section. */
#view-list.dv2-grocery .to-buy-badge {
  background: hsl(28 90% 52%);
  color: #fff;
}

/* G3 (approved), revised H4 (2026-08-17) — the badge stops being a read-only
   progress ring and becomes a BASKET control: it adds the whole section to the
   basket. It used to be a tick, which marked the section bought; the glyph now
   says what the control actually does. The ring and the tick are hidden rather
   than removed, because sectionHTML() is shared markup and must keep emitting
   both for Trips. The click is intercepted in app.js by a selector that carries
   Gate B, so the badge stays a ring on every other list. */
#view-list.dv2-grocery .section-head .badge {
  width: 24px; height: 24px;
  border-radius: 50%;
  border: 1.8px solid var(--dv2-text-muted);
  cursor: pointer;
  transition: background .2s, border-color .2s, transform .15s;
}
#view-list.dv2-grocery .section-head .badge:active { transform: scale(.88); }
#view-list.dv2-grocery .section-head .badge .ring { display: none; }
#view-list.dv2-grocery .section-head .badge .tick { display: none; }
/* The basket glyph is Grocery-only: everywhere else the badge is still a ring
   and must not grow a second icon. */
.section-head .badge .basket { display: none; }
#view-list.dv2-grocery .section-head .badge .basket {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%; height: 100%;
  color: var(--dv2-text-muted);
  transition: color .2s;
}
/* Filled = the whole section is already in the basket. This is deliberately
   keyed off `.wanted-all` (want_buy) and NOT `.done` (bought): the control
   flags items to buy, so its state must mirror that and nothing else. */
#view-list.dv2-grocery .section.wanted-all .badge {
  background: var(--dv2-accent);
  border-color: var(--dv2-accent);
}
#view-list.dv2-grocery .section.wanted-all .badge .basket { color: #fff; }


/* ── Rows ─────────────────────────────────────────────────────────────────
   Each row is its own card under b121 (`.list .row-swipe`, style.css:7680),
   filled with the opaque `var(--surface)` white. Design V2 makes that card
   glass so the aurora carries through the whole list; the `.row` inside it
   goes transparent so there is only one surface, not two stacked.

   style.css tints the label with the section hue (`.section .row-swipe .label`,
   0,3,0). Design V2 reads item text as plain primary, so that is overridden
   here at (1,4,0) — for grocery only. */
#view-list.dv2-grocery .row-swipe {
  border-radius: 16px;
  background: var(--dv2-groc-card-bg);
  border: 1px solid var(--dv2-groc-card-bd);
  -webkit-backdrop-filter: blur(20px) saturate(var(--dv2-g-sat));
  backdrop-filter: blur(20px) saturate(var(--dv2-g-sat));
  box-shadow: var(--dv2-g-shadow);
}
#view-list.dv2-grocery .row {
  gap: 12px;
  padding: 12px 16px;
  border-radius: 16px;
  background: transparent;
  border-top: none;
}
/* ── Row alignment ────────────────────────────────────────────────────────
   `.row` is `align-items: center` (style.css:1722), which centres each child
   on the ROW. `.label-wrap` is a block, so when an item carries a second line
   — `.meta` ("bought by Ritesh Lal · Sat"), a note, or the store chips — the
   item NAME is no longer at the row's centre, while the basket and tick still
   are. Measured on a seeded row: name centre 28.50px from the row top,
   basket and tick 36.70px. 8.20px apart. Single-line rows measure 0.00px
   apart, which is why only some rows look wrong.

   Fix: align the controls to the first line box instead of to the row.
   --dv2-row-line1 is the rendered height of `.label-row`; it is set by the
   33px assignee chip that row always contains, not by the 22.4px text line.
   Predates Design V2 — the same 8.20px offset measures with the gate removed
   — so the fix is gated to Grocery and Tasks/Trips keep their current spacing. */
#view-list.dv2-grocery .row {
  --dv2-row-line1: 33px;
  align-items: flex-start;
}
#view-list.dv2-grocery .row > .buy-flag {
  margin-top: calc((var(--dv2-row-line1) - 28px) / 2);
}
#view-list.dv2-grocery .row > .box {
  margin-top: calc((var(--dv2-row-line1) - 24px) / 2);
}
/* Keeps the grip's -5px side margins (style.css:1659); only the top changes. */
#view-list.dv2-grocery .row > .item-grip {
  margin-top: calc((var(--dv2-row-line1) - 34px) / 2);
}

#view-list.dv2-grocery .row:active { background: var(--dv2-g-07-bg); }
/* b121's amber "just added" highlight is (0,3,0) and the transparent fill
   above is (1,2,0), so it would be lost without restating it. */
#view-list.dv2-grocery .row.fresh { background: hsl(45 100% 50% / .16); }

/* NOTE. `.label-row` wraps (style.css:2577) and `.assign-inline` is pushed
   right with `margin-left: auto`, so on a longer label the assignee chip drops
   to a line of its own and the card grows to ~110px of mostly empty space.
   Holding the row to one line was tried and rejected: it ellipsised real item
   names ("Sweet pot…"), which is worse than a tall card. Tightening this
   properly means moving `.assign-inline` out of `.label-row`, which is shared
   markup, so it is left for a later phase. */
#view-list.dv2-grocery .section .row-swipe .label {
  font-size: 16px; font-weight: 600; letter-spacing: -.01em;
  color: var(--dv2-text-primary);
}
#view-list.dv2-grocery .section .row-swipe .row.on .label {
  color: var(--dv2-text-muted);
  font-weight: 500;
  text-decoration: line-through;
}
#view-list.dv2-grocery .row.on { opacity: .55; }

#view-list.dv2-grocery .box {
  width: 24px; height: 24px;
  border: 1.8px solid var(--dv2-text-muted);
}
#view-list.dv2-grocery .row.on .box {
  background: var(--dv2-accent);
  border-color: var(--dv2-accent);
}

/* The "want to buy" flag is the one deliberately non-purple control on the
   screen: amber is the app's established "someone has asked for this" colour
   and Phase C (purchase requests) builds directly on it. */
#view-list.dv2-grocery .buy-flag {
  width: 28px; height: 28px;
  border: 1.5px dashed var(--dv2-text-muted);
  color: var(--dv2-text-muted);
}
#view-list.dv2-grocery .row.wanted { background: hsl(28 90% 52% / .12); }
#view-list.dv2-grocery .row.wanted .label { font-weight: 700; }

#view-list.dv2-grocery .qty-badge {
  background: var(--dv2-g-10-bg);
  border: .75px solid var(--dv2-g-07-bd);
  color: var(--dv2-text-sub);
  font-weight: 700;
}
#view-list.dv2-grocery .row-note { color: var(--dv2-text-sub); }
#view-list.dv2-grocery .meta { color: var(--dv2-text-muted); }
#view-list.dv2-grocery .task-history-line { color: var(--dv2-text-sub); }
#view-list.dv2-grocery .task-history-line .thl-label { color: var(--dv2-text-muted); }
#view-list.dv2-grocery .task-history-line .thl-more { color: var(--dv2-link); }
#view-list.dv2-grocery .item-grip { color: var(--dv2-text-muted); }
#view-list.dv2-grocery .item-grip:active { background: var(--dv2-g-10-bg); }


/* ── Assignee chip ────────────────────────────────────────────────────────
   Rendered by assigneeCircles() into `.assign-inline` on every row and already
   fully functional (tap → assign menu → POST /items/{id}/assign). Only its
   colour scheme changes: the hue-derived pastel becomes glass, so the row's
   accent stays with the store chips and the amber "wanted" state. */
#view-list.dv2-grocery .lp-assign-chip {
  background: var(--dv2-g-10-bg);
  border: .75px solid var(--dv2-g-07-bd);
  color: var(--dv2-text-sub);
}
#view-list.dv2-grocery .lp-assign-chip.unassigned {
  background: transparent;
  border-color: var(--dv2-divider);
  color: var(--dv2-text-muted);
}
#view-list.dv2-grocery .lp-assign-chip.unassigned .av {
  background: var(--dv2-g-07-bg);
  color: var(--dv2-text-muted);
}


/* ── Store chips (Gate C) ─────────────────────────────────────────────────
   Rendered by shopRowHTML() from `item.shops`, which has always existed and
   has always been editable in the item sheet — the row simply never showed it.
   Pulled forward from Phase B because it is pure rendering of existing data
   with no backend change, and it is visible in the Design V2 screenshots. */
#view-list.dv2-grocery .dv2-store-line {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin-top: 6px;
}
#view-list.dv2-grocery .dv2-store-chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 9px 3px 7px;
  border-radius: 9999px;
  background: var(--dv2-g-10-bg);
  border: .75px solid var(--dv2-g-07-bd);
  color: var(--dv2-text-sub);
  font-size: 11px; font-weight: 600; letter-spacing: .01em;
  white-space: nowrap;
}
#view-list.dv2-grocery .dv2-store-chip svg { flex: none; opacity: .7; }
#view-list.dv2-grocery .row.on .dv2-store-chip { opacity: .6; }


/* ── Send bar (G6) ────────────────────────────────────────────────────────
   Sticky, inside #view-list. Three states, all pre-existing except the third:
   "Send N to <person>" (flagged, unsent), "Mark all bought" (flagged, sent) —
   and the new resting state, which appears whenever anything is unbought. */
#view-list.dv2-grocery .send-bar {
  padding: 12px 0 calc(14px + env(safe-area-inset-bottom));
  background: none;
}
#view-list.dv2-grocery .send-bar .btn {
  height: 52px;
  border-radius: 9999px;
  border: 0;
  background: var(--dv2-btn-primary);
  box-shadow: var(--dv2-btn-shadow);
  color: #fff;
  font-family: inherit;
  font-size: 16px; font-weight: 700; letter-spacing: -.01em;
}
#view-list.dv2-grocery .send-bar.done .btn {
  background: var(--dv2-btn-primary);
}
#view-list.dv2-grocery .send-bar .btn:disabled {
  background: var(--dv2-btn-disabled-bg);
  box-shadow: none;
  color: var(--dv2-btn-disabled-tx);
}


/* ── FAB ──────────────────────────────────────────────────────────────────
   `#btn-shop-add.fab` is (1,1,0) in style.css and fills itself with the list
   hue. Rooting at Gate B makes this (2,1,0). */
#view-list.dv2-grocery #btn-shop-add.fab {
  background: var(--dv2-btn-primary);
  box-shadow: var(--dv2-btn-shadow);
  color: #fff;
}

/* G6 keeps the send bar up whenever anything is unbought, which on grocery is
   most of the time — and style.css parks the FAB at the same `bottom: 22px`,
   so the two sit on top of each other. `.fab-wrap` is inside #view-list
   (index.html:1653), so Gate B can lift it clear of the 52px pill without the
   body half. The `:has()` keeps the FAB in its normal place on a fully bought
   list, where the bar is hidden and there is nothing to clear. */
#view-list.dv2-grocery:has(#send-bar:not([hidden])) .fab-wrap {
  bottom: calc(env(safe-area-inset-bottom) + 88px);
}


/* ── Options sheet ────────────────────────────────────────────────────────
   `#side-panel` is INSIDE #view-list (index.html:1574), so Gate B reaches it
   without needing the body half. */
#view-list.dv2-grocery .lp-sheet {
  background: var(--dv2-g-10-bg);
  border: 1px solid var(--dv2-g-10-bd);
  -webkit-backdrop-filter: blur(28px) saturate(var(--dv2-g-sat));
  backdrop-filter: blur(28px) saturate(var(--dv2-g-sat));
  box-shadow: var(--dv2-g-shadow);
  color: var(--dv2-text-primary);
}
/* `.lp-sheet-title` is painted with a hue gradient through `background-clip:
   text` and `-webkit-text-fill-color: transparent` (style.css:5581), so a
   `color` on its own is ignored — the fill has to be reset too. */
#view-list.dv2-grocery .lp-sheet-title {
  background: none;
  -webkit-text-fill-color: var(--dv2-text-primary);
  color: var(--dv2-text-primary);
}
#view-list.dv2-grocery .lp-grab { background: var(--dv2-text-muted); }


/* ══════════════════════════════════════════════════════════════════════════
   GATE B, body half — the aurora, and the sheets that live OUTSIDE #view-list
   ══════════════════════════════════════════════════════════════════════════
   `#sheet-filter` (index.html:1786), `#sheet-shop-item` (1895) and
   `#sheet-suggest` (2150) are siblings of the view, so no descendant selector
   from `#view-list.dv2-grocery` can reach them. `.lp-assign-menu` is in the
   same position for a different reason: openAssignMenu() appends it to
   document.body so it can escape the list's scroll container.

   renderShop() sets `body.dv2-groc` when the open list is grocery; show()
   removes it for any view other than 'list'. Every selector below names one of
   those three sheet ids or `.lp-assign-menu`, so the class can only ever
   affect grocery's own overlays.
   ────────────────────────────────────────────────────────────────────────── */

/* ── Aurora backdrop ──────────────────────────────────────────────────────
   The dashboard gets its aurora from a real `.dv2-aurora` element inside
   `#view-dashboard`. `#view-list` is shared markup, so adding one there would
   put a div in Tasks' and Trips' DOM — this phase paints it instead.

   It has to be painted on <body>, not on `#view-list.dv2-grocery`. The view's
   box ends at the last section, and style.css leaves a tall bottom gutter
   under it for the tab bar; a background on the view therefore stops in the
   middle of the screen on a scrolled list and the gutter shows through as a
   hard seam. <body> always covers the viewport. `background-attachment: fixed`
   keeps the blobs still while the list scrolls under them.

   The `--dv2-*` set is hosted on `#view-list.dv2-grocery` (design-v2-app.css
   :38 dark, :155 light), which is a descendant of <body>, not an ancestor —
   so the aurora tokens have to be re-declared here, per theme. */
html[data-theme="dark"] body.dv2-groc {
  --dv2-bg-grad: radial-gradient(ellipse 90% 70% at 20% 10%, #1a0845 0%, #06001a 45%, #000d18 100%);
  --dv2-blob1: hsla(270,70%,45%,0.55); --dv2-blob2: hsla(185,80%,35%,0.45);
  --dv2-blob3: hsla(330,65%,40%,0.35); --dv2-blob4: hsla(240,70%,38%,0.3);
}
html[data-theme="light"] body.dv2-groc {
  --dv2-bg-grad: radial-gradient(ellipse 90% 70% at 20% 10%, #DDD5FF 0%, #EEF2FF 50%, #DFF4F0 100%);
  --dv2-blob1: hsla(270,70%,75%,0.35); --dv2-blob2: hsla(185,80%,65%,0.28);
  --dv2-blob3: hsla(330,65%,75%,0.22); --dv2-blob4: hsla(240,60%,75%,0.2);
}
body.dv2-groc {
  background:
    radial-gradient(circle at 8% 2%,   var(--dv2-blob1) 0%, transparent 45%),
    radial-gradient(circle at 98% 86%, var(--dv2-blob2) 0%, transparent 48%),
    radial-gradient(circle at 94% 10%, var(--dv2-blob3) 0%, transparent 38%),
    radial-gradient(circle at 2% 72%,  var(--dv2-blob4) 0%, transparent 42%),
    var(--dv2-bg-grad);
  background-attachment: fixed, fixed, fixed, fixed, fixed;
  background-repeat: no-repeat;
}


/* The token block in design-v2-app.css is hosted on `#view-list.dv2-grocery`,
   which is not an ancestor of these sheets — so `--dv2-*` does NOT inherit
   down here. Re-host the handful the sheets actually use, per theme.

   A1-a adds #sheet-rename-section. Measured, not assumed: without it the D13
   rule below resolved `var(--dv2-text-primary)` to nothing, the whole
   `-webkit-text-fill-color` declaration was dropped as invalid, and the title
   fell back to the `color: hsl(var(--hue) …)` in style.css — so the gradient
   went but the hue tint stayed. Adding a title rule without adding the sheet
   here is a silent no-op. */
html[data-theme="dark"] body.dv2-groc #sheet-filter,
html[data-theme="dark"] body.dv2-groc #sheet-shop-item,
html[data-theme="dark"] body.dv2-groc #sheet-rename-section,
html[data-theme="dark"] body.dv2-groc #sheet-section-store,
html[data-theme="dark"] body.dv2-groc #sheet-section-family,
html[data-theme="dark"] body.dv2-groc #sheet-suggest {
  --dv2-text-primary: rgba(255,255,255,0.95);
  --dv2-text-sub: rgba(255,255,255,0.45);
  --dv2-text-muted: rgba(255,255,255,0.32);
  --dv2-g-07-bg: rgba(255,255,255,0.07); --dv2-g-07-bd: rgba(255,255,255,0.12);
  --dv2-g-10-bg: rgba(28,20,58,0.72);    --dv2-g-10-bd: rgba(255,255,255,0.16);
  --dv2-divider: rgba(255,255,255,0.08);
  --dv2-accent: hsla(270,70%,65%,1);
  --dv2-link: hsla(270,70%,72%,1);
  --dv2-btn-primary: linear-gradient(135deg,hsla(270,70%,55%,1) 0%,hsla(290,65%,50%,1) 50%,hsla(250,75%,60%,1) 100%);
  --dv2-btn-shadow: 0 8px 32px hsla(270,70%,40%,0.5), 0 2px 8px rgba(0,0,0,0.3);
  --dv2-input-bg: rgba(255,255,255,0.07); --dv2-input-bd: rgba(255,255,255,0.15);
  --dv2-g-sat: 180%;
}
html[data-theme="light"] body.dv2-groc #sheet-filter,
html[data-theme="light"] body.dv2-groc #sheet-shop-item,
html[data-theme="light"] body.dv2-groc #sheet-rename-section,
html[data-theme="light"] body.dv2-groc #sheet-section-store,
html[data-theme="light"] body.dv2-groc #sheet-section-family,
html[data-theme="light"] body.dv2-groc #sheet-suggest {
  --dv2-text-primary: rgba(14,10,38,0.92);
  --dv2-text-sub: rgba(14,10,38,0.45);
  --dv2-text-muted: rgba(14,10,38,0.32);
  --dv2-g-07-bg: rgba(255,255,255,0.50); --dv2-g-07-bd: rgba(255,255,255,0.75);
  --dv2-g-10-bg: rgba(252,251,255,0.88); --dv2-g-10-bd: rgba(255,255,255,0.95);
  --dv2-divider: rgba(14,10,38,0.06);
  --dv2-accent: hsla(270,70%,52%,1);
  --dv2-link: hsla(270,70%,52%,1);
  --dv2-btn-primary: linear-gradient(135deg,hsla(270,65%,50%,1) 0%,hsla(290,60%,48%,1) 50%,hsla(250,70%,56%,1) 100%);
  --dv2-btn-shadow: 0 8px 28px hsla(270,65%,45%,0.4), 0 2px 6px rgba(0,0,0,0.12);
  --dv2-input-bg: rgba(255,255,255,0.75); --dv2-input-bd: rgba(255,255,255,0.95);
  --dv2-g-sat: 200%;
}

body.dv2-groc #sheet-filter,
body.dv2-groc #sheet-shop-item,
body.dv2-groc #sheet-suggest {
  font-family: 'Plus Jakarta Sans', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  background: var(--dv2-g-10-bg);
  border: 1px solid var(--dv2-g-10-bd);
  -webkit-backdrop-filter: blur(28px) saturate(var(--dv2-g-sat));
  backdrop-filter: blur(28px) saturate(var(--dv2-g-sat));
  color: var(--dv2-text-primary);
}

body.dv2-groc #sheet-filter h2,
body.dv2-groc #sheet-shop-item h2,
body.dv2-groc #sheet-suggest h2,
body.dv2-groc #sheet-filter h3,
body.dv2-groc #sheet-shop-item h3,
body.dv2-groc #sheet-suggest h3 {
  color: var(--dv2-text-primary);
  letter-spacing: -.02em;
}
/* Same gradient-clipped fill as the options panel title — see the note there. */
/* A1-a (D13) adds #sheet-rename-section here. openSectionRename() sets --hue on
   that sheet from the SECTION's stored colour, and with the picker hidden that
   left a title tinted by a colour the reader cannot see anywhere else on
   Grocery and cannot change. Neutralising it is one more selector on a rule
   that already exists, not a new mechanism. Non-Grocery keeps the tint. */
body.dv2-groc #sheet-filter .lp-sheet-title,
body.dv2-groc #sheet-shop-item .lp-sheet-title,
body.dv2-groc #sheet-suggest .lp-sheet-title,
/* A1-a (D13). Gated on .a1a-clean as well, unlike its three siblings above:
   those sheets have no colour control to come back, this one does. Flipping
   SECTION_MENU_V2 off must return the picker AND the tint together, or the
   rollback is only half a rollback. Caught by the flag-off negative control. */
body.dv2-groc.a1a-clean #sheet-rename-section .lp-sheet-title,
/* C1-b - same treatment, ungated: this sheet only exists under the flag. */
body.dv2-groc #sheet-section-store .lp-sheet-title,
body.dv2-groc #sheet-section-family .lp-sheet-title {
  background: none;
  -webkit-text-fill-color: var(--dv2-text-primary);
}

/* The item sheet's header confirm control is the list-hue blue disc. */
body.dv2-groc #sheet-shop-item .ios26-check {
  background: var(--dv2-btn-primary);
  box-shadow: var(--dv2-btn-shadow);
  color: #fff;
}

/* DELIVERABLE 5 — store assignment. The per-store rows inside the Shop picker
   carry the selected state, and style.css sets both the row fill and the store
   name with `!important` (style.css:7230), so those two have to be answered in
   kind. The tick itself is not `!important`, so (1,3,0) is enough. */
body.dv2-groc #sheet-shop-item .shop-roll-row.on {
  background: hsl(270 70% 52% / .10) !important;
}
body.dv2-groc #sheet-shop-item .shop-roll-row.on .srr-name {
  color: var(--dv2-accent) !important;
}
body.dv2-groc #sheet-shop-item .shop-roll-row.on .srr-tick {
  background: var(--dv2-accent);
  border-color: var(--dv2-accent);
  color: #fff;
}
body.dv2-groc #sheet-filter label,
body.dv2-groc #sheet-shop-item label,
body.dv2-groc #sheet-suggest label { color: var(--dv2-text-sub); }

/* Filter chips (Store / Category / Assignee) and the item sheet's store and
   assignee pickers all render as `.chip` today. */
body.dv2-groc #sheet-filter .chip,
body.dv2-groc #sheet-shop-item .chip,
body.dv2-groc #sheet-suggest .chip {
  border-radius: 9999px;
  background: var(--dv2-g-07-bg);
  border: 1px solid var(--dv2-g-07-bd);
  color: var(--dv2-text-primary);
  font-family: inherit;
  font-weight: 600;
}
body.dv2-groc #sheet-filter .chip.on,
body.dv2-groc #sheet-shop-item .chip.on,
body.dv2-groc #sheet-suggest .chip.on {
  background: var(--dv2-accent);
  border-color: transparent;
  color: #fff;
}

body.dv2-groc #sheet-filter input,
body.dv2-groc #sheet-shop-item input,
body.dv2-groc #sheet-suggest input,
body.dv2-groc #sheet-shop-item textarea,
body.dv2-groc #sheet-shop-item select {
  background: var(--dv2-input-bg);
  border: 1px solid var(--dv2-input-bd);
  border-radius: 14px;
  color: var(--dv2-text-primary);
  font-family: inherit;
}

body.dv2-groc #sheet-filter .btn.primary,
body.dv2-groc #sheet-shop-item .btn.primary,
body.dv2-groc #sheet-suggest .btn.primary {
  border: 0;
  border-radius: 9999px;
  background: var(--dv2-btn-primary);
  box-shadow: var(--dv2-btn-shadow);
  color: #fff;
  font-family: inherit;
  font-weight: 700;
}
body.dv2-groc #sheet-filter .btn.ghost,
body.dv2-groc #sheet-shop-item .btn.ghost,
body.dv2-groc #sheet-suggest .btn.ghost {
  border-radius: 9999px;
  background: var(--dv2-g-07-bg);
  border: 1px solid var(--dv2-g-07-bd);
  color: var(--dv2-text-primary);
  font-family: inherit;
}


/* Assignee menu — same body-appended problem as the sheets, same gate. */
html[data-theme="dark"] body.dv2-groc .lp-assign-menu {
  --dv2-text-primary: rgba(255,255,255,0.95);
  --dv2-text-sub: rgba(255,255,255,0.45);
  --dv2-g-07-bg: rgba(255,255,255,0.07);
  --dv2-g-10-bg: rgba(28,20,58,0.86); --dv2-g-10-bd: rgba(255,255,255,0.16);
  --dv2-divider: rgba(255,255,255,0.08);
  --dv2-accent: hsla(270,70%,65%,1);
}
html[data-theme="light"] body.dv2-groc .lp-assign-menu {
  --dv2-text-primary: rgba(14,10,38,0.92);
  --dv2-text-sub: rgba(14,10,38,0.45);
  --dv2-g-07-bg: rgba(255,255,255,0.50);
  --dv2-g-10-bg: rgba(252,251,255,0.94); --dv2-g-10-bd: rgba(255,255,255,0.95);
  --dv2-divider: rgba(14,10,38,0.06);
  --dv2-accent: hsla(270,70%,52%,1);
}
body.dv2-groc .lp-assign-menu {
  font-family: 'Plus Jakarta Sans', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  background: var(--dv2-g-10-bg);
  border: 1px solid var(--dv2-g-10-bd);
  border-radius: 16px;
  -webkit-backdrop-filter: blur(24px) saturate(180%);
  backdrop-filter: blur(24px) saturate(180%);
  color: var(--dv2-text-primary);
}
body.dv2-groc .lp-assign-menu .head { color: var(--dv2-text-muted, rgba(120,120,140,.8)); }
body.dv2-groc .lp-assign-menu .opt { color: var(--dv2-text-primary); border-radius: 10px; }
body.dv2-groc .lp-assign-menu .opt.on {
  background: var(--dv2-g-07-bg);
  color: var(--dv2-text-primary);
}
body.dv2-groc .lp-assign-menu .opt .tick { color: var(--dv2-accent); }
body.dv2-groc .lp-assign-menu .sep { border-top-color: var(--dv2-divider); }


/* ── Deterministic screenshots ─────────────────────────────────────────────
   Set by the capture driver on <html>, never by app.js. Pins the entry
   animations at their end state so before/after runs are diffable. */
html[data-dv2-static="1"] #view-list.dv2-grocery .section,
html[data-dv2-static="1"] #view-list.dv2-grocery .dv2-gempty {
  animation: none;
  opacity: 1;
  transform: none;
}


/* ══════════════════════════════════════════════════════════════════════════
   The Information (ⓘ) sheet — #sheet-welcome            GATE B, body half
   ══════════════════════════════════════════════════════════════════════════
   `#btn-shop-info` (index.html:1530) opens `openWelcome()` (app.js), which
   fills `#sheet-welcome` (index.html:2191). Phase A restyled #sheet-filter,
   #sheet-shop-item and #sheet-suggest; this sheet was missed, so it still
   showed the pre-V2 green gradient hero and emoji rows.

   `openWelcome()` is shared by EVERY list, so all of this is gated on
   `body.dv2-groc` — the class renderShop() sets for the Grocery surface only.
   Tasks and Trips open the same element and see none of it. The emoji→line
   icon swap is gated the same way, in app.js.

   Same token-rehosting problem as the three sheets above: the `--dv2-*` block
   is hosted on `#view-list.dv2-grocery`, which is not an ancestor of a sheet
   that sits beside the view. Re-declare what this sheet uses, per theme.
   ────────────────────────────────────────────────────────────────────────── */

html[data-theme="dark"] body.dv2-groc #sheet-welcome {
  --dv2-wl-bar-bg: rgba(22,16,46,0.96);
  --dv2-text-primary: rgba(255,255,255,0.95);
  --dv2-text-sub: rgba(255,255,255,0.62);
  --dv2-text-muted: rgba(255,255,255,0.42);
  --dv2-g-07-bg: rgba(255,255,255,0.07); --dv2-g-07-bd: rgba(255,255,255,0.12);
  --dv2-g-10-bg: rgba(28,20,58,0.72);    --dv2-g-10-bd: rgba(255,255,255,0.16);
  --dv2-divider: rgba(255,255,255,0.08);
  --dv2-link: hsla(270,70%,72%,1);
  --dv2-btn-primary: linear-gradient(135deg,hsla(270,70%,55%,1) 0%,hsla(290,65%,50%,1) 50%,hsla(250,75%,60%,1) 100%);
  --dv2-btn-shadow: 0 8px 32px hsla(270,70%,40%,0.5), 0 2px 8px rgba(0,0,0,0.3);
  --dv2-g-sat: 180%;
  /* The hero and the row tiles take their hue from the list itself: openWelcome
     already sets --c / --c-deep on #welcome-hero from the guide hue. */
  --dv2-wl-tile-bg: rgba(255,255,255,0.08);
  --dv2-wl-tile-bd: rgba(255,255,255,0.14);
  --dv2-wl-tile-ic: rgba(255,255,255,0.82);
  --dv2-wl-hero-tint: 0.22;
}
html[data-theme="light"] body.dv2-groc #sheet-welcome {
  --dv2-wl-bar-bg: rgba(250,249,255,0.97);
  --dv2-text-primary: rgba(14,10,38,0.92);
  --dv2-text-sub: rgba(14,10,38,0.58);
  --dv2-text-muted: rgba(14,10,38,0.42);
  --dv2-g-07-bg: rgba(255,255,255,0.50); --dv2-g-07-bd: rgba(255,255,255,0.75);
  --dv2-g-10-bg: rgba(252,251,255,0.88); --dv2-g-10-bd: rgba(255,255,255,0.95);
  --dv2-divider: rgba(14,10,38,0.06);
  --dv2-link: hsla(270,70%,52%,1);
  --dv2-btn-primary: linear-gradient(135deg,hsla(270,65%,50%,1) 0%,hsla(290,60%,48%,1) 50%,hsla(250,70%,56%,1) 100%);
  --dv2-btn-shadow: 0 8px 28px hsla(270,65%,45%,0.4), 0 2px 6px rgba(0,0,0,0.12);
  --dv2-g-sat: 200%;
  --dv2-wl-tile-bg: rgba(255,255,255,0.72);
  --dv2-wl-tile-bd: rgba(255,255,255,0.95);
  --dv2-wl-tile-ic: rgba(14,10,38,0.72);
  --dv2-wl-hero-tint: 0.12;
}

/* ── The sheet surface ──────────────────────────────────────────────────── */

body.dv2-groc #sheet-welcome {
  font-family: 'Plus Jakarta Sans', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  background: var(--dv2-g-10-bg);
  border: 1px solid var(--dv2-g-10-bd);
  -webkit-backdrop-filter: blur(28px) saturate(var(--dv2-g-sat));
  backdrop-filter: blur(28px) saturate(var(--dv2-g-sat));
  color: var(--dv2-text-primary);
}

/* ── Hero ───────────────────────────────────────────────────────────────
   Was a saturated green gradient card with white text. Design V2 has no
   solid-fill headers on glass: it becomes a tinted glass panel that carries
   the list hue at low opacity, with the type in the sheet's own colours. */

body.dv2-groc #sheet-welcome .welcome-hero {
  border-radius: 24px;
  padding: 22px 20px 20px;
  margin: 10px 0 14px;
  color: var(--dv2-text-primary);
  background:
    linear-gradient(160deg,
      hsl(from var(--c, #4aa3ff) h s l / var(--dv2-wl-hero-tint)),
      transparent 70%),
    var(--dv2-g-07-bg);
  border: 1px solid var(--dv2-g-07-bd);
  text-align: center;
}
/* `hsl(from …)` is Chrome/Safari-only. Where relative colour is unsupported the
   first layer is dropped and the panel is plain glass, which is still correct
   Design V2 — the hue is decoration, not information. */
@supports not (color: hsl(from red h s l)) {
  body.dv2-groc #sheet-welcome .welcome-hero { background: var(--dv2-g-07-bg); }
}

/* The hero glyph sits in the same tile the activity cards and quick-add rows
   use, so the sheet reads as part of the same system. */
body.dv2-groc #sheet-welcome .welcome-emoji {
  width: 56px; height: 56px; margin: 0 auto;
  border-radius: 20px;
  display: grid; place-items: center;
  background: var(--dv2-wl-tile-bg);
  border: 1px solid var(--dv2-wl-tile-bd);
  font-size: 26px; line-height: 1;
}
/* GP-1 · was a Material Symbols ligature, now a `.dv2-lineic` <svg>. That
   class sizes itself at 1em, so font-size is still what drives it. */
body.dv2-groc #sheet-welcome .dv2-wl-hero-ic {
  font-size: 28px;
  color: var(--dv2-wl-tile-ic);
}
body.dv2-groc #sheet-welcome .welcome-hero h2 {
  margin: 14px 0 4px;
  font-size: 24px; font-weight: 700; letter-spacing: -.02em;
  color: var(--dv2-text-primary);
}
body.dv2-groc #sheet-welcome .welcome-tag {
  font-family: 'Inter', -apple-system, sans-serif;
  font-size: 13.5px; font-weight: 400; line-height: 1.5;
  color: var(--dv2-text-sub);
  opacity: 1;
}

/* The close control was an accent-blue disc sized for a coloured hero. On
   glass it becomes the same ghost disc the other Design V2 sheets use. */
body.dv2-groc #sheet-welcome .welcome-x {
  width: 32px; height: 32px;
  background: var(--dv2-g-07-bg);
  border: 1px solid var(--dv2-g-07-bd);
  color: var(--dv2-text-sub);
  box-shadow: none;
}
body.dv2-groc #sheet-welcome .welcome-x:active { transform: scale(.92); }

/* ── Feature rows ────────────────────────────────────────────────────────
   Hairline-separated rows become individually rounded glass cards — the same
   one-card-per-row pattern the list screen uses. */

body.dv2-groc #sheet-welcome .welcome-feats {
  display: flex; flex-direction: column; gap: 8px;
  margin: 0 0 4px;
}
body.dv2-groc #sheet-welcome .welcome-feat {
  align-items: flex-start;
  gap: 14px;
  padding: 14px;
  border: 1px solid var(--dv2-g-07-bd);
  border-bottom: 1px solid var(--dv2-g-07-bd);
  border-radius: 18px;
  background: var(--dv2-g-07-bg);
}
body.dv2-groc #sheet-welcome .wf-e {
  flex: 0 0 40px;
  width: 40px; height: 40px;
  border-radius: 14px;
  display: grid; place-items: center;
  background: var(--dv2-wl-tile-bg);
  border: 1px solid var(--dv2-wl-tile-bd);
  font-size: 18px;
}
body.dv2-groc #sheet-welcome .dv2-wf-ic {
  font-size: 20px;
  color: var(--dv2-wl-tile-ic);
}
body.dv2-groc #sheet-welcome .wf-t {
  font-size: 14.5px; font-weight: 700; letter-spacing: -.01em;
  color: var(--dv2-text-primary);
}
body.dv2-groc #sheet-welcome .wf-d {
  font-family: 'Inter', -apple-system, sans-serif;
  font-size: 12.5px; font-weight: 400; line-height: 1.5;
  color: var(--dv2-text-sub);
  margin-top: 3px;
}

/* ── Actions ────────────────────────────────────────────────────────────── */

/* `.sheet` carries 40px of bottom padding, and a sticky element is held inside
   the padding box — so the bar floated 40px clear of the sheet's bottom edge
   and the last feature row showed through the gap underneath it. The padding
   stands down here; the bar supplies its own, safe-area included, already. */
body.dv2-groc #sheet-welcome { padding-bottom: 0; }

/* The action bar is `position: sticky`, so it needs a backdrop of its own —
   `background: none` let the feature rows scroll through it, and the sheet's
   own 0.72-alpha glass was still too sheer to read against. This is the one
   near-opaque surface in the sheet, which is what the legacy bar was too. */
body.dv2-groc #sheet-welcome .sheet-actions {
  background: var(--dv2-wl-bar-bg);
  border-top: 1px solid var(--dv2-divider);
  gap: 8px;
}
body.dv2-groc #sheet-welcome .sheet-actions .btn {
  border-radius: 16px;
  min-height: 50px;
  font-family: 'Plus Jakarta Sans', -apple-system, sans-serif;
  font-size: 15px; font-weight: 700; letter-spacing: -.01em;
  background: var(--dv2-g-07-bg);
  border: 1px solid var(--dv2-g-07-bd);
  color: var(--dv2-text-primary);
}
body.dv2-groc #sheet-welcome .sheet-actions .btn.primary {
  background: var(--dv2-btn-primary);
  border: 0;
  box-shadow: var(--dv2-btn-shadow);
  color: #fff;
}
body.dv2-groc #sheet-welcome .sheet-actions .btn.primary {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
}
body.dv2-groc #sheet-welcome .dv2-wl-btn-ic {
  font-size: 19px;
}


/* ══════════════════════════════════════════════════════════════════════════
   PHASE A.5 — the AI list build: #sheet-dv2-ai and #sheet-dv2-ai-review
   ══════════════════════════════════════════════════════════════════════════
   Two sheets added in this phase (index.html, just below #sheet-suggest).
   They are siblings of `#view-list`, so — exactly like #sheet-filter,
   #sheet-shop-item, #sheet-suggest and #sheet-welcome above — they are reached
   through the body half of Gate B, `body.dv2-groc`, and re-host the `--dv2-*`
   tokens per theme because the token block lives on `#view-list.dv2-grocery`,
   which is not an ancestor of a sheet that sits beside the view.

   Both sheets are NEW markup with `dv2-` prefixed ids and classes, opened only
   by openDv2AiBuild(), which returns early unless isGroceryList() is true. So
   nothing here can reach Tasks, Trips or any other list — the gate is belt and
   braces, not the only protection.
   ────────────────────────────────────────────────────────────────────────── */

html[data-theme="dark"] body.dv2-groc #sheet-dv2-ai,
html[data-theme="dark"] body.dv2-groc #sheet-dv2-ai-review {
  --dv2-text-primary: rgba(255,255,255,0.95);
  --dv2-text-sub: rgba(255,255,255,0.55);
  --dv2-text-muted: rgba(255,255,255,0.34);
  --dv2-g-07-bg: rgba(255,255,255,0.07); --dv2-g-07-bd: rgba(255,255,255,0.12);
  --dv2-g-10-bg: rgba(22,16,46,0.82);    --dv2-g-10-bd: rgba(255,255,255,0.16);
  --dv2-divider: rgba(255,255,255,0.08);
  --dv2-accent: hsla(270,70%,65%,1);
  --dv2-btn-primary: linear-gradient(135deg,hsla(270,70%,55%,1) 0%,hsla(290,65%,50%,1) 50%,hsla(250,75%,60%,1) 100%);
  --dv2-btn-shadow: 0 8px 32px hsla(270,70%,40%,0.5), 0 2px 8px rgba(0,0,0,0.3);
  --dv2-ai-bar-bg: rgba(22,16,46,0.96);
  --dv2-ai-track: rgba(255,255,255,0.10);
  --dv2-ai-row-bg: rgba(255,255,255,0.05);
  --dv2-ai-row-on: hsla(270,70%,62%,0.16);
  --dv2-ai-row-on-bd: hsla(270,70%,68%,0.42);
  --dv2-g-sat: 180%;
}
html[data-theme="light"] body.dv2-groc #sheet-dv2-ai,
html[data-theme="light"] body.dv2-groc #sheet-dv2-ai-review {
  --dv2-text-primary: rgba(14,10,38,0.92);
  --dv2-text-sub: rgba(14,10,38,0.55);
  --dv2-text-muted: rgba(14,10,38,0.34);
  --dv2-g-07-bg: rgba(255,255,255,0.55); --dv2-g-07-bd: rgba(255,255,255,0.80);
  --dv2-g-10-bg: rgba(250,249,255,0.88); --dv2-g-10-bd: rgba(255,255,255,0.95);
  --dv2-divider: rgba(14,10,38,0.07);
  --dv2-accent: hsla(270,70%,52%,1);
  --dv2-btn-primary: linear-gradient(135deg,hsla(270,65%,50%,1) 0%,hsla(290,60%,48%,1) 50%,hsla(250,70%,56%,1) 100%);
  --dv2-btn-shadow: 0 8px 28px hsla(270,65%,45%,0.4), 0 2px 6px rgba(0,0,0,0.12);
  --dv2-ai-bar-bg: rgba(250,249,255,0.97);
  --dv2-ai-track: rgba(14,10,38,0.09);
  --dv2-ai-row-bg: rgba(255,255,255,0.62);
  --dv2-ai-row-on: hsla(270,70%,55%,0.12);
  --dv2-ai-row-on-bd: hsla(270,70%,52%,0.38);
  --dv2-g-sat: 200%;
}

/* ── Shared surface ───────────────────────────────────────────────────────── */

body.dv2-groc #sheet-dv2-ai,
body.dv2-groc #sheet-dv2-ai-review {
  font-family: 'Plus Jakarta Sans', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  background: var(--dv2-g-10-bg);
  border: 1px solid var(--dv2-g-10-bd);
  border-bottom: 0;
  border-radius: 28px 28px 0 0;
  -webkit-backdrop-filter: blur(28px) saturate(var(--dv2-g-sat));
  backdrop-filter: blur(28px) saturate(var(--dv2-g-sat));
  color: var(--dv2-text-primary);
  /* Same reason as #sheet-welcome: `.sheet` carries 40px of bottom padding and
     a sticky element is held inside the padding box, so the action bar would
     float clear of the sheet's bottom edge. The bar brings its own padding. */
  padding-bottom: 0;
}
body.dv2-groc #sheet-dv2-ai .grabber,
body.dv2-groc #sheet-dv2-ai-review .grabber { background: var(--dv2-text-muted); }

/* The universal ✕ injected by ensureSheetCloseButtons(). The operation stays;
   the legacy solid disc is replaced by the same ghost-glass treatment the
   (ⓘ) sheet's own close control uses. */
body.dv2-groc #sheet-dv2-ai .sheet-close,
body.dv2-groc #sheet-dv2-ai-review .sheet-close {
  width: 34px; height: 34px;
  border-radius: 9999px;
  background: var(--dv2-g-07-bg);
  border: 1px solid var(--dv2-g-07-bd);
  color: var(--dv2-text-sub);
  box-shadow: none;
}
body.dv2-groc #sheet-dv2-ai .sheet-close:active,
body.dv2-groc #sheet-dv2-ai-review .sheet-close:active { transform: scale(.92); }

body.dv2-groc #sheet-dv2-ai h2,
body.dv2-groc #sheet-dv2-ai-review h2 {
  color: var(--dv2-text-primary);
  font-size: 21px; font-weight: 700; letter-spacing: -.02em;
  margin: 0 0 8px;
}
body.dv2-groc #sheet-dv2-ai-review .hint {
  color: var(--dv2-text-sub);
  font-size: 13.5px;
  margin: 0 0 12px;
}

/* ── Action bar (both sheets) ─────────────────────────────────────────────── */

body.dv2-groc #sheet-dv2-ai .sheet-actions,
body.dv2-groc #sheet-dv2-ai-review .sheet-actions {
  background: var(--dv2-ai-bar-bg);
  border-top: 1px solid var(--dv2-divider);
  gap: 8px;
}
body.dv2-groc #sheet-dv2-ai .sheet-actions .btn,
body.dv2-groc #sheet-dv2-ai-review .sheet-actions .btn {
  border-radius: 16px;
  min-height: 50px;
  font-family: inherit;
  font-size: 15px; font-weight: 700; letter-spacing: -.01em;
  background: var(--dv2-g-07-bg);
  border: 1px solid var(--dv2-g-07-bd);
  color: var(--dv2-text-primary);
}
body.dv2-groc #sheet-dv2-ai .sheet-actions .btn.primary,
body.dv2-groc #sheet-dv2-ai-review .sheet-actions .btn.primary {
  background: var(--dv2-btn-primary);
  border: 0;
  box-shadow: var(--dv2-btn-shadow);
  color: #fff;
  flex: 1.4;
}
body.dv2-groc #sheet-dv2-ai-review .sheet-actions .btn.primary:disabled {
  opacity: .45;
  box-shadow: none;
}

/* ── Consent and building panes ───────────────────────────────────────────── */

body.dv2-groc #sheet-dv2-ai .dv2-ai-pane { text-align: center; }
body.dv2-groc #sheet-dv2-ai .dv2-ai-pane .sheet-actions { text-align: left; }
/* The consent pane's bottom inset comes from its sticky action bar. The
   building pane has no bar, and the sheet's own bottom padding stands down
   above — so it supplies its own, or the last step clips at the screen edge. */
body.dv2-groc #sheet-dv2-ai #dv2-ai-building {
  padding-bottom: calc(env(safe-area-inset-bottom) + 30px);
}

body.dv2-groc #sheet-dv2-ai .dv2-ai-orb {
  display: grid; place-items: center;
  width: 62px; height: 62px;
  margin: 4px auto 16px;
  border-radius: 22px;
  background: var(--dv2-btn-primary);
  box-shadow: var(--dv2-btn-shadow);
  color: #fff;
}
body.dv2-groc #sheet-dv2-ai .dv2-ai-sub {
  color: var(--dv2-text-sub);
  font-size: 14px; line-height: 1.55;
  margin: 0 auto 18px;
  max-width: 34ch;
}
body.dv2-groc #sheet-dv2-ai .dv2-ai-points {
  list-style: none;
  margin: 0 0 8px;
  padding: 0;
  text-align: left;
  display: flex; flex-direction: column; gap: 2px;
}
body.dv2-groc #sheet-dv2-ai .dv2-ai-points li {
  display: flex; align-items: flex-start; gap: 11px;
  padding: 11px 14px;
  border-radius: 16px;
  background: var(--dv2-g-07-bg);
  border: 1px solid var(--dv2-g-07-bd);
  color: var(--dv2-text-primary);
  font-size: 13.5px; line-height: 1.45;
}
body.dv2-groc #sheet-dv2-ai .dv2-ai-points li + li { margin-top: 8px; }
body.dv2-groc #sheet-dv2-ai .dv2-ai-points svg {
  flex: none;
  margin-top: 2px;
  color: var(--dv2-accent);
}
body.dv2-groc #sheet-dv2-ai .dv2-ai-note {
  color: var(--dv2-text-muted);
  font-size: 12.5px;
  margin: 12px 0 0;
}

/* Smart list type (A.6 item 1). Segmented pills rather than a <select>: with
   two to four options the whole choice should be readable without a tap, and
   the blurb is what makes "Vegan" unambiguous. Sized to stay legible at the
   fourth option, so Vegetarian/Gluten-Free/High Protein need no CSS change. */
body.dv2-groc #sheet-dv2-ai .dv2-ai-diet {
  margin: 16px 0 0;
}
body.dv2-groc #sheet-dv2-ai .dv2-ai-diet[hidden] { display: none; }
body.dv2-groc #sheet-dv2-ai .dv2-ai-diet-lbl {
  display: block;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--dv2-text-muted);
  margin: 0 0 8px;
}
body.dv2-groc #sheet-dv2-ai .dv2-ai-diet-opts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 8px;
}
/* Same tokens as the review sheet's pickable rows — this is the same kind of
   control, so it should not invent a second selected-state colour. */
body.dv2-groc #sheet-dv2-ai .dv2-ai-diet-opt {
  display: block;
  width: 100%;
  text-align: left;
  padding: 10px 12px;
  border: 1px solid var(--dv2-g-07-bd);
  border-radius: 14px;
  background: var(--dv2-ai-row-bg);
  color: var(--dv2-text-primary);
  cursor: pointer;
  transition: border-color .16s ease, background .16s ease;
}
body.dv2-groc #sheet-dv2-ai .dv2-ai-diet-opt.on {
  border-color: var(--dv2-ai-row-on-bd);
  background: var(--dv2-ai-row-on);
}
body.dv2-groc #sheet-dv2-ai .dv2-ai-diet-name {
  display: block;
  font-size: 14px;
  font-weight: 650;
}
body.dv2-groc #sheet-dv2-ai .dv2-ai-diet-opt.on .dv2-ai-diet-name { color: var(--dv2-accent); }
body.dv2-groc #sheet-dv2-ai .dv2-ai-diet-blurb {
  display: block;
  font-size: 11.5px;
  line-height: 1.35;
  color: var(--dv2-text-muted);
  margin-top: 2px;
}

/* The type carries through to review, so the list can be identified there. */
body.dv2-groc #sheet-dv2-ai-review .dv2-air-diet {
  margin-right: auto;
  padding: 3px 10px;
  border-radius: 9999px;
  border: 1px solid var(--dv2-accent);
  color: var(--dv2-accent);
  font-size: 11.5px;
  font-weight: 700;
  white-space: nowrap;
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-diet[hidden] { display: none; }

/* Building — the orb breathes rather than spins: a spinner promises
   determinate progress the single round trip cannot report. */
@keyframes dv2-ai-breathe {
  0%, 100% { transform: scale(1);    box-shadow: var(--dv2-btn-shadow); }
  50%      { transform: scale(1.06); box-shadow: var(--dv2-btn-shadow); }
}
body.dv2-groc #sheet-dv2-ai .dv2-ai-orb-live {
  animation: dv2-ai-breathe 1.6s ease-in-out infinite;
}
body.dv2-groc #sheet-dv2-ai .dv2-ai-bar {
  height: 6px;
  border-radius: 9999px;
  background: var(--dv2-ai-track);
  overflow: hidden;
  margin: 4px 0 18px;
}
body.dv2-groc #sheet-dv2-ai .dv2-ai-bar > span {
  display: block;
  height: 100%;
  width: 16%;
  border-radius: 9999px;
  background: var(--dv2-btn-primary);
  transition: width .5s cubic-bezier(.3,.8,.3,1);
}
body.dv2-groc #sheet-dv2-ai .dv2-ai-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  text-align: left;
  display: flex; flex-direction: column; gap: 8px;
}
body.dv2-groc #sheet-dv2-ai .dv2-ai-steps li {
  display: flex; align-items: center; gap: 11px;
  padding: 11px 14px;
  border-radius: 16px;
  background: var(--dv2-g-07-bg);
  border: 1px solid var(--dv2-g-07-bd);
  color: var(--dv2-text-muted);
  font-size: 13.5px;
  transition: color .25s ease, border-color .25s ease;
}
body.dv2-groc #sheet-dv2-ai .dv2-ai-steps li.now { color: var(--dv2-text-primary); }
body.dv2-groc #sheet-dv2-ai .dv2-ai-steps li.done {
  color: var(--dv2-text-sub);
  border-color: var(--dv2-ai-row-on-bd);
}
body.dv2-groc #sheet-dv2-ai .dv2-ai-tick {
  flex: none;
  display: grid; place-items: center;
  width: 18px; height: 18px;
  border-radius: 9999px;
  border: 1.6px solid var(--dv2-text-muted);
  transition: background .25s ease, border-color .25s ease;
}
/* Borders-and-rotate check rather than an inline SVG, so the tick element in
   index.html stays an empty span the JS never has to rewrite. */
body.dv2-groc #sheet-dv2-ai .dv2-ai-tick::after {
  content: '';
  width: 4px; height: 8px;
  margin-top: -2px;
  border: 2px solid #fff;
  border-top: 0; border-left: 0;
  border-radius: 1px;
  transform: rotate(42deg);
  opacity: 0;
  transition: opacity .2s ease;
}
body.dv2-groc #sheet-dv2-ai .dv2-ai-steps li.done .dv2-ai-tick::after { opacity: 1; }
body.dv2-groc #sheet-dv2-ai .dv2-ai-steps li.now .dv2-ai-tick {
  border-color: var(--dv2-accent);
}
body.dv2-groc #sheet-dv2-ai .dv2-ai-steps li.done .dv2-ai-tick {
  background: var(--dv2-accent);
  border-color: var(--dv2-accent);
}

/* ── Review sheet ─────────────────────────────────────────────────────────── */

/* ensureSheetCloseButtons() (app.js) appends a ✕ to every sheet — a behavioural
   affordance, so it is kept and restyled rather than suppressed. The head row
   reserves space for it so "Select all" cannot sit underneath. */
body.dv2-groc #sheet-dv2-ai-review .dv2-air-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding-right: 46px;
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-head h2 { margin: 0; }
body.dv2-groc #sheet-dv2-ai-review #btn-dv2-air-all {
  flex: none;
  margin: 0;
  min-height: 34px;
  padding: 0 14px;
  border-radius: 9999px;
  background: var(--dv2-g-07-bg);
  border: 1px solid var(--dv2-g-07-bd);
  color: var(--dv2-text-primary);
  font-family: inherit;
  font-size: 12.5px; font-weight: 600;
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-list {
  display: flex; flex-direction: column; gap: 14px;
  max-height: 54vh;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-bottom: 4px;
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-group {
  border-radius: 20px;
  background: var(--dv2-g-07-bg);
  border: 1px solid var(--dv2-g-07-bd);
  padding: 12px 12px 14px;
  transition: opacity .2s ease;
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-group.empty { opacity: .55; }
body.dv2-groc #sheet-dv2-ai-review .dv2-air-ghead {
  display: flex; align-items: center; gap: 9px;
  margin: 0 2px 10px;
}
/* GP-1 · was the GROCERY_SECTIONS emoji. Now the section's line icon, in a
   glass disc so the group head reads as a Design V2 header rather than a
   sticker. Same 30px footprint the emoji occupied. */
body.dv2-groc #sheet-dv2-ai-review .dv2-air-gemoji {
  flex: none;
  width: 30px; height: 30px;
  border-radius: 10px;
  display: grid; place-items: center;
  background: var(--dv2-g-07-bg);
  border: 1px solid var(--dv2-g-07-bd);
  color: var(--dv2-text-sub);
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-gic { width: 17px; height: 17px; }
/* style.css:1933 shouts every `.sheet h3` in uppercase with wide tracking.
   Design V2 titles are sentence case — the legacy visual rule yields. */
body.dv2-groc #sheet-dv2-ai-review .dv2-air-ghead h3 {
  flex: 1;
  margin: 0;
  color: var(--dv2-text-primary);
  font-family: inherit;
  font-size: 14.5px; font-weight: 700; letter-spacing: -.01em;
  text-transform: none;
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-gcount {
  color: var(--dv2-text-muted);
  font-size: 12px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-gtoggle {
  flex: none;
  padding: 4px 10px;
  border-radius: 9999px;
  background: none;
  border: 1px solid var(--dv2-g-07-bd);
  color: var(--dv2-text-sub);
  font-family: inherit;
  font-size: 11.5px; font-weight: 600;
  cursor: pointer;
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-gtoggle:active { transform: scale(.94); }
body.dv2-groc #sheet-dv2-ai-review .dv2-air-rows {
  display: flex; flex-wrap: wrap; gap: 7px;
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-row {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 13px 8px 9px;
  border-radius: 9999px;
  background: var(--dv2-ai-row-bg);
  border: 1px solid var(--dv2-g-07-bd);
  color: var(--dv2-text-sub);
  font-family: inherit;
  font-size: 13.5px; font-weight: 600; letter-spacing: -.01em;
  cursor: pointer;
  transition: background .16s ease, border-color .16s ease, color .16s ease, transform .12s ease;
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-row:active { transform: scale(.96); }
body.dv2-groc #sheet-dv2-ai-review .dv2-air-row.on {
  background: var(--dv2-ai-row-on);
  border-color: var(--dv2-ai-row-on-bd);
  color: var(--dv2-text-primary);
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-check {
  flex: none;
  display: grid; place-items: center;
  width: 19px; height: 19px;
  border-radius: 9999px;
  border: 1.6px solid var(--dv2-text-muted);
  color: transparent;
  transition: background .16s ease, border-color .16s ease, color .16s ease;
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-row.on .dv2-air-check {
  background: var(--dv2-accent);
  border-color: var(--dv2-accent);
  color: #fff;
}
/* "Bought most weeks". A word-length badge cost ~80px on every chip and pushed
   the flow to one item per line; a star costs ~12px and carries the same
   signal. Same convention the trip picker already uses (`.suggest-star`). */
body.dv2-groc #sheet-dv2-ai-review .dv2-air-star {
  flex: none;
  margin-left: -2px;
  display: inline-flex;
  color: var(--dv2-text-muted);
  line-height: 1;
}
/* GP-1 · was a ★ character. Same signal, same footprint, stroked. */
body.dv2-groc #sheet-dv2-ai-review .dv2-air-star .dv2-lineic {
  width: 12px; height: 12px; stroke-width: 1.9;
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-row.on .dv2-air-star {
  color: var(--dv2-accent);
}

/* Deterministic screenshots — pin the entry animation and the breathing orb. */
html[data-dv2-static="1"] body.dv2-groc #sheet-dv2-ai,
html[data-dv2-static="1"] body.dv2-groc #sheet-dv2-ai-review { animation: none; }
html[data-dv2-static="1"] body.dv2-groc #sheet-dv2-ai .dv2-ai-orb-live {
  animation: none;
  transform: none;
}


/* ══════════════════════════════════════════════════════════════════════════
   GP-1 · ONE ICON LANGUAGE — Grocery half
   ══════════════════════════════════════════════════════════════════════════

   design-v2-app.css owns the base `.dv2-lineic` rule and the legacy/Design V2
   twin swap. This block is the Grocery-gated half: it retires the pastel
   coloured tiles and emoji discs that the old system used to carry a glyph,
   and restages them as the same glass square the rest of Grocery uses.

   Everything here is behind `body.dv2-groc` or `#view-list.dv2-grocery`, so a
   Tasks or Trips list opening the very same sheet is untouched.
   ────────────────────────────────────────────────────────────────────────── */

/* ── Section grid tiles (G7) ─────────────────────────────────────────────
   Was a 22px emoji. Now a glass disc holding the section's line icon, which
   is what makes the empty state stop showing two icon systems at once — the
   Design V2 cart hero sat directly above twelve full-colour emoji. */
#view-list.dv2-grocery .gsec-e {
  flex: none;
  width: 30px; height: 30px;
  border-radius: 10px;
  display: grid; place-items: center;
  background: var(--dv2-g-07-bg);
  border: 1px solid var(--dv2-g-07-bd);
  color: var(--dv2-text-sub);
}
#view-list.dv2-grocery .gsec-ic { width: 18px; height: 18px; }
#view-list.dv2-grocery .gsec-n { flex: 1; }

/* ── Cascade sheet head ───────────────────────────────────────────────── */
body.dv2-groc #sheet-gcascade .gc-emoji {
  width: 38px; height: 38px;
  border-radius: 12px;
  display: grid; place-items: center;
  background: var(--dv2-g-07-bg);
  border: 1px solid var(--dv2-g-07-bd);
  color: var(--dv2-text-sub);
}
body.dv2-groc #sheet-gcascade .gc-emoji-ic { width: 21px; height: 21px; }

/* ── Sheet header tiles ───────────────────────────────────────────────────
   `.lp-sheet-icon` is a saturated hue gradient (style.css:5569) — a 36px
   coloured chip carrying an emoji. #sheet-filter additionally forced it
   orange. Both are legacy; Design V2 sheet heads use the same glass square as
   every other Grocery surface, and the glyph inside is the line icon twin. */
body.dv2-groc .lp-sheet-icon,
body.dv2-groc .lp-sheet-icon--filter {
  background: var(--dv2-g-07-bg);
  border: 1px solid var(--dv2-g-07-bd);
  border-radius: 12px;
  color: var(--dv2-text-primary);
  box-shadow: none;
}
body.dv2-groc .lp-sheet-icon .dv2-lineic { width: 20px; height: 20px; }
/* The Grocery list's own header tile, set by openPanel(). */
body.dv2-groc .lp-sheet-lineic { width: 20px; height: 20px; }

/* ── Filter sheet rows ────────────────────────────────────────────────────
   The "Full List" and "To Buy Only" thumbnails: 40px pastel circles (#e6ecff
   and #ffe8d6) each carrying an emoji. Same treatment as every other Grocery
   glyph holder now. */
body.dv2-groc .filt-ic,
body.dv2-groc .filt-ic--all,
body.dv2-groc .filt-ic--buy {
  width: 38px; height: 38px;
  border-radius: 12px;
  background: var(--dv2-g-07-bg);
  border: 1px solid var(--dv2-g-07-bd);
  color: var(--dv2-text-sub);
}
body.dv2-groc .filt-ic .dv2-lineic { width: 20px; height: 20px; }
/* The selected row lifts its glyph to the accent, the way the Design V2 rows
   already mark selection. */
body.dv2-groc .filt-row[aria-pressed="true"] .filt-ic,
body.dv2-groc .filt-row.on .filt-ic { color: var(--dv2-accent); }

/* ── List-options panel rows ──────────────────────────────────────────────
   `.lp-lic` is a 28px hue-tinted square (style.css:5654). Two of its four
   glyphs were FILLED (Suggest items, Delete this list) and two were stroked —
   so the sheet mixed weights as well as systems. */
body.dv2-groc .lp-lic {
  border-radius: 9px;
  background: var(--dv2-g-07-bg);
  border: 1px solid var(--dv2-g-07-bd);
  color: var(--dv2-text-sub);
}
body.dv2-groc .lp-lic .dv2-lineic { width: 16px; height: 16px; }
/* The two glyphs that were already stroked came in at stroke-width 2. */
body.dv2-groc .lp-lic svg { stroke-width: 1.7; }
/* style.css:7578 (`.panel-item svg`) gives EVERY svg in a panel row its own
   30px blue rounded tile — chevrons included. That is a second, older icon-tile
   system layered underneath `.lp-lic`, so the glass square was drawn around a
   blue square. Strip the inner tile here and let `.lp-lic` be the only one. */
body.dv2-groc #side-panel .panel-item svg {
  width: 16px; height: 16px;
  padding: 0;
  border-radius: 0;
  background: none;
  color: inherit;
}
/* Matched by the rule above too, so it has to out-specify it: the chevron is a
   direction marker, not a row glyph, and must not take the label's colour. */
body.dv2-groc #side-panel .panel-item svg.lp-chev,
body.dv2-groc #side-panel svg.lp-drop-chev {
  width: 14px; height: 14px;
  color: var(--dv2-text-muted);
}
/* Destructive stays destructive — GP-1 changes the language, not the meaning. */
body.dv2-groc .lp-destr .lp-lic {
  background: rgba(255,90,90,.12);
  border-color: rgba(255,90,90,.28);
  color: #ff6b6b;
}

/* ── List toolbar ─────────────────────────────────────────────────────────
   #btn-shop-info was the only filled glyph among five: a solid dark disc
   sitting between four 2px strokes. The twin restores the set. */
#view-list.dv2-grocery .lp-tool .lp-dv2-ic { display: inline-block; }

/* ── Item rows: the unassigned "add person" chip ──────────────────────────
   Last Material Symbol left on a Grocery row. `.dv2-lineic` sizes at 1em and
   style.css:8417 already sets that to 18px, so only the colour is restated. */
body.dv2-groc .lp-assign-chip.assign-compact .lp-assign-add.lp-dv2-ic {
  color: var(--dv2-text-muted);
}

/* ── A.6 · Dietary tags ───────────────────────────────────────────────────
   D-I: vegan items are BADGED, never moved into a section of their own, so a
   mixed household still shops from one list. Gated to `body.dv2-groc` like
   everything else here — Tasks and Trips never render the badge because the
   markup itself is behind isGroceryList(), and this rule only styles it.

   Green is used deliberately and only here: it is the one colour a shopper
   already reads as "plant" without a legend. Sized off the qty badge it sits
   next to so the label row keeps a single rhythm. */
body.dv2-groc .dv2-diet-badge {
  flex: none;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: .04em;
  line-height: 1;
  padding: 3px 5px;
  border-radius: 5px;
  color: #37b26d;
  background: rgba(55, 178, 109, .13);
  border: 1px solid rgba(55, 178, 109, .3);
}
/* A bought row greys out entirely; the badge must not stay the one bright
   thing on it. */
body.dv2-groc .row.on .dv2-diet-badge {
  opacity: .45;
}

/* Legend in the ⓘ sheet. It answers a question rather than announcing a
   feature, so it is deliberately quieter than the feature cards above it:
   same card geometry and the same rehosted --dv2-g-07-* tokens so it belongs
   to the sheet, but smaller type and no icon tile, so the eye reads it last.
   The badge inside it is a real .dv2-diet-badge styled by the rule above —
   the legend cannot drift from what the rows actually draw.

   Only #sheet-welcome carries the rehosted tokens (see the theme blocks
   further up), so the selector is scoped there and not to .dv2-groc alone. */
body.dv2-groc #sheet-welcome .welcome-diet-legend {
  margin: 8px 0 4px;
  padding: 12px 14px;
  border: 1px solid var(--dv2-g-07-bd);
  border-radius: 18px;
  background: var(--dv2-g-07-bg);
}
body.dv2-groc #sheet-welcome .wdl-t {
  margin: 0 0 8px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--dv2-text-muted);
}
body.dv2-groc #sheet-welcome .wdl-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-size: 13px;
  color: var(--dv2-text-primary);
}
body.dv2-groc #sheet-welcome .wdl-d {
  margin: 8px 0 0;
  font-size: 12px;
  line-height: 1.45;
  color: var(--dv2-text-muted);
}

/* Dietary filter chips reuse .shop-chips/.chip wholesale, so there is no
   layout CSS here on purpose. The group only needs to wrap — six options is
   more than the by-shop row ever carries, and a horizontal scroller would
   hide "Hide vegan" off the right edge where nobody would find it. */
body.dv2-groc #filter-diet-chips {
  flex-wrap: wrap;
  overflow-x: visible;
}

/* The wrapped group adds ~150px to a sheet that was already close to its
   ceiling, which tipped #sheet-filter past `.sheet.compact`'s max-height: 80vh
   and made it scroll for the first time. That matters because
   `.sheet.compact .sheet-actions` sets `position: static` on purpose — fine
   while everything fits, but once it scrolls, "Apply" sits at the bottom of
   748px of content inside a 673px window, i.e. off-screen on a 390×844 phone
   with no visible way to commit the filter you just chose. Pin it, and stand
   the sheet's bottom padding down so it pins flush rather than floating clear.
   Gated to body.dv2-groc, and this sheet is SHARED — Tasks, Trips and every
   other list have no dietary group, still fit, and keep the exact sheet they
   had. --card-surface / --hair are the legacy tokens `.sheet` itself uses, so
   the bar is opaque in both themes without re-hosting any --dv2-* token. */
body.dv2-groc #sheet-filter { padding-bottom: 0; }
body.dv2-groc #sheet-filter .sheet-actions {
  position: sticky;
  bottom: 0;
  margin: var(--sp-4) calc(var(--sp-5) * -1) 0;
  padding: var(--sp-3) var(--sp-5) var(--sp-4);
  background: var(--card-surface);
  border-top: .5px solid var(--hair);
}

/* ─── Scan S1 · top-bar entry point ─────────────────────────────────────────
   The ⓘ button moves into the List options sheet ON GROCERY ONLY, and the
   camera takes the slot it vacates. Done in CSS, gated on body.dv2-groc, for
   the same reason the GP-1 icon twins are: every other list must keep the
   toolbar it has always had, ⓘ included. The button itself is never removed
   from the DOM — its handler is still wired, and #panel-info calls the same
   openWelcome(), so the A.6 dietary badge legend inside that sheet is
   reachable exactly as before, one tap further in.

   Filter, collapse and clear ticks are deliberately untouched. The A.6
   dietary filter is the control §12.7 argues always applies, and burying it
   to make the bar shorter would undo a decision taken two phases ago. */
body.dv2-groc #btn-shop-info { display: none; }

/* The camera is `hidden` in the markup and stays that way until S2 sets
   SCAN_ENABLED — this rule only describes what it looks like once shown, so
   S1 ships an entry point with nothing behind it and no way to tap it. */
body.dv2-groc #btn-shop-scan .dv2-lineic {
  width: 22px;
  height: 22px;
  /* H2 — the glyph is a flex child of a 42px-tall `.lp-tool`; without
     `flex: none` a narrow tool bar can shrink it along the main axis and
     square it off, and `overflow: visible` means a stroke that ever lands on
     the viewBox edge still draws rather than being cut. The touch target is
     the button (42px), not the glyph, so neither affects tap size. */
  flex: none;
  overflow: visible;
}

/* The `hidden` attribute alone does NOT hide either of these. `.lp-tool` sets
   `display: inline-flex` (style.css:5442) and `.panel-item` / `.lp-row` set
   `display: flex` (style.css:565, 5643); a class rule beats the UA-level
   `[hidden] { display: none }`, so the element stays visible with the
   attribute set. These two rules are what actually make `hidden` mean hidden,
   and without them S1 would ship a live camera button with nothing behind it.
   Same guard the dashboard already uses (design-v2-app.css:517). */
#btn-shop-scan[hidden] { display: none !important; }
#panel-info[hidden]    { display: none !important; }

/* H3 — the scan review sheet now carries THREE actions (Cancel · Add to List ·
   Add to Buy). At 390px the default `.btn` padding pushed "Add to List" onto two
   lines while its neighbours stayed on one, which made a pair of equal choices
   look like two different kinds of control. Cancel gives up its equal share of
   the row (it is the smallest label and the least likely action), and the two
   destinations keep their labels on one line. Scoped to this sheet, so no other
   action row is touched. */
#sheet-dv2-ai-review .sheet-actions .btn {
  padding-left: 10px;
  padding-right: 10px;
  white-space: nowrap;
}
#sheet-dv2-ai-review .sheet-actions #btn-dv2-air-cancel { flex: 0 1 auto; }

/* The Grocery Information row in the List options sheet. Inherits .panel-item
   /.lp-row geometry untouched; this only sizes the line icon to match the
   rows above and below it, which carry 16px glyphs. */
body.dv2-groc #panel-info .lp-lic .dv2-lineic {
  width: 16px;
  height: 16px;
}

/* ─── Scan S2 · Scan Your List ──────────────────────────────────────────────
   The scan sheet reuses .dv2-ai-pane / .dv2-ai-orb / .dv2-ai-bar / .dv2-ai-steps
   wholesale — it is the same wait, so it should look like the same wait. Only
   the source buttons and the unreadable block are new. Everything is scoped
   under body.dv2-groc, as every grocery rule since A.6 has been. */
body.dv2-groc #sheet-dv2-scan .dv2-scan-src {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 14px 0 4px;
}
body.dv2-groc #sheet-dv2-scan .dv2-scan-btn {
  width: 100%;
  justify-content: flex-start;
  font-weight: 600;
}
/* A disabled source is one this deployment genuinely cannot serve (PDF without
   a direct Anthropic key). Dimmed rather than hidden, because the note beside
   it explains why — silently missing options invite "where did it go". */
body.dv2-groc #sheet-dv2-scan .dv2-scan-btn:disabled {
  opacity: .45;
  pointer-events: none;
}

/* Unreadable lines, in the review sheet. Deliberately quieter than the items
   above it — it is a caveat, not the content — but present, because a short
   list that looks complete is the failure this feature cannot have. */
body.dv2-groc #sheet-dv2-ai-review .dv2-air-unread {
  margin: 2px 4px 12px;
  padding: 10px 12px;
  border-radius: 12px;
  background: rgba(190, 140, 40, .09);
  border: 1px solid rgba(190, 140, 40, .22);
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-unread[hidden] { display: none; }
body.dv2-groc #sheet-dv2-ai-review .dv2-air-unread-t {
  margin: 0 0 6px;
  font-size: 13px;
  font-weight: 700;
  color: #8a6410;
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-unread ul {
  margin: 0;
  padding-left: 0;                 /* M1 — rows, not bullets */
  list-style: none;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--dv2-ink-2, #55606b);
}

/* ─── M1 · manual entry for unreadable lines ──────────────────────────────
   Each row is quoted context + an EMPTY field. The quote is deliberately
   quieter than the input: it is a memory aid for what was on the paper, not a
   suggestion, and it must not read as a value waiting to be accepted. */
body.dv2-groc #sheet-dv2-ai-review .dv2-air-mrow {
  padding: 7px 0;
  border-top: 1px solid rgba(190, 140, 40, .16);
  position: relative;              /* M2 — anchor for the suggestion list */
}

/* ─── S4-v1 · the advisory context strip ───────────────────────────────────
   Deliberately quiet. It is a question about the page, not an alert, and it
   must not compete with the item list or the action buttons. */
body.dv2-groc #sheet-dv2-ai-review .dv2-air-ctx {
  margin: 2px 4px 10px;
  padding: 10px 12px;
  border-radius: 12px;
  background: rgba(124, 58, 237, .07);
  border: 1px solid rgba(124, 58, 237, .20);
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-ctx[hidden] { display: none; }
body.dv2-groc #sheet-dv2-ai-review .dv2-air-ctx-t {
  margin: 0 0 8px;
  font-size: 13px;
  font-weight: 600;
  color: var(--dv2-ink-1, #1d2530);
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-ctx-acts {
  display: flex;
  gap: 8px;
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-ctx .btn.small {
  flex: 0 1 auto;
  padding: 7px 12px;
  font-size: 13px;
  border-radius: 9px;
  white-space: nowrap;
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-ctx .btn.small[hidden] { display: none; }

/* ─── M3-lite · rows rendered by kind ──────────────────────────────────────
   A whole-image verdict ("blank white page, no text or list visible") is a
   statement about the photograph, not a line anyone can fill in. It used to
   render as a typeable row, which invited the shopper to name an item that
   does not exist. It is now a sentence. */
body.dv2-groc #sheet-dv2-ai-review .dv2-air-mnote {
  padding: 7px 0;
  border-top: 1px solid rgba(190, 140, 40, .16);
  font-size: 12px;
  font-style: italic;
  color: var(--dv2-ink-3, #7b858f);
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-mnote:first-child { border-top: 0; }

/* "Use <text>" (a pre-filled partial read) and "Put <name> back" (a struck
   item). Both are offers: quieter than the field they act on, so neither
   reads as the primary thing to do. */
body.dv2-groc #sheet-dv2-ai-review .dv2-air-muse,
body.dv2-groc #sheet-dv2-ai-review .dv2-air-mback {
  margin: 6px 0 0;
  padding: 5px 10px;
  border-radius: 8px;
  border: 1px solid rgba(190, 140, 40, .35);
  background: transparent;
  font-size: 12px;
  font-weight: 600;
  color: #8a6410;
  cursor: pointer;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-muse:active,
body.dv2-groc #sheet-dv2-ai-review .dv2-air-mback:active {
  background: rgba(190, 140, 40, .12);
}
/* A pre-filled row is visibly a SUGGESTION until the shopper opts in, so a
   value they did not type never looks like one they did. */
body.dv2-groc #sheet-dv2-ai-review .dv2-air-minput[data-suggested="1"] {
  color: var(--dv2-ink-3, #7b858f);
  font-style: italic;
  border-style: dashed;
}

/* ─── M2 · suggestions from the household's own items ──────────────────────
   A custom listbox rather than <datalist>: datalist rendering is unreliable on
   iOS Safari, which is the primary target here, and it cannot carry the second
   line (section + how often) that makes a suggestion recognisable as THEIR
   item rather than a generic word. */
body.dv2-groc #sheet-dv2-ai-review .dv2-sugg {
  position: absolute;
  z-index: 30;
  left: 0;
  /* The list hangs BELOW the input, so it does not need to clear the section
     select sitting beside it — constraining it to 42% only made longer item
     names wrap onto two lines for no benefit. */
  right: 0;
  top: calc(100% - 4px);
  margin: 0;
  padding: 4px;
  list-style: none;
  border-radius: 10px;
  background: var(--dv2-surface, #fff);
  border: 1px solid rgba(0, 0, 0, .10);
  box-shadow: 0 10px 28px rgba(20, 22, 32, .18);
  max-height: 184px;
  overflow-y: auto;
}
/* Flip above when the row sits low in the sheet, so the list can never cover
   Add to List / Add to Buy. */
body.dv2-groc #sheet-dv2-ai-review .dv2-sugg.up {
  top: auto;
  bottom: calc(100% - 30px);
}
body.dv2-groc #sheet-dv2-ai-review .dv2-sugg-o {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 9px;
  border-radius: 7px;
  cursor: pointer;
}
body.dv2-groc #sheet-dv2-ai-review .dv2-sugg-o.on,
body.dv2-groc #sheet-dv2-ai-review .dv2-sugg-o:hover {
  background: rgba(124, 58, 237, .10);
}
body.dv2-groc #sheet-dv2-ai-review .dv2-sugg-l {
  font-size: 14px;
  font-weight: 600;
  color: var(--dv2-ink-1, #1d2530);
}
body.dv2-groc #sheet-dv2-ai-review .dv2-sugg-m {
  font-size: 11px;
  white-space: nowrap;
  color: var(--dv2-ink-3, #7b858f);
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-mrow:first-child { border-top: 0; }
body.dv2-groc #sheet-dv2-ai-review .dv2-air-mtext {
  margin: 0 0 5px;
  font-size: 12px;
  font-style: italic;
  color: var(--dv2-ink-3, #7b858f);
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-mentry {
  display: flex;
  gap: 6px;
  align-items: center;
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-minput {
  flex: 1 1 auto;
  min-width: 0;
  /* 16px keeps iOS Safari from zooming the sheet on focus, which on a phone
     in a shop throws the rest of the review off-screen mid-edit. */
  font-size: 16px;
  padding: 8px 10px;
  border-radius: 9px;
  border: 1px solid rgba(190, 140, 40, .35);
  background: var(--dv2-surface, #fff);
  color: var(--dv2-ink-1, #1d2530);
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-minput:focus {
  outline: none;
  border-color: var(--dv2-accent, #7c3aed);
  box-shadow: 0 0 0 3px rgba(124, 58, 237, .16);
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-msec {
  flex: 0 0 auto;
  max-width: 42%;
  font-size: 13px;
  padding: 8px 6px;
  border-radius: 9px;
  border: 1px solid rgba(190, 140, 40, .35);
  background: var(--dv2-surface, #fff);
  color: var(--dv2-ink-2, #55606b);
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-unread-d {
  margin: 6px 0 0;
  font-size: 11.5px;
  color: var(--dv2-ink-3, #7b858f);
}

/* ─── Scan S3 · vegan substitution, shown on the row ────────────────────────
   R2 — silent content substitution — is the highest risk in this feature. A
   swap the shopper cannot see IS that failure, so every rewritten row carries
   the words they actually wrote, directly beneath the label. Quieter than the
   label but not hidden: it is a caveat on the row, not decoration. */
body.dv2-groc #sheet-dv2-ai-review .dv2-air-raw {
  display: block;
  margin-top: 2px;
  font-size: 11px;
  font-weight: 500;
  line-height: 1.25;
  color: var(--dv2-ink-3, #7b858f);
  font-style: italic;
}
body.dv2-groc #sheet-dv2-ai-review .dv2-air-row.on .dv2-air-raw { opacity: .85; }


/* ==========================================================================
   A2a-1 - Grocery hamburger cleanup (D2/A, D7, D9)
   --------------------------------------------------------------------------
   #side-panel and #sheet-welcome live OUTSIDE #view-list, so a selector rooted
   at #view-list.dv2-grocery cannot reach them - body.dv2-groc is the only way
   in (see the note in show(), app.js). .a2a-clean is the feature flag half:
   renderShop() sets it from isGroc && A2A_MENU_CLEANUP, so flipping the flag
   restores every control with no other change.

   HIDE ONLY. Every node below stays in the DOM with its handler bound:
     #panel-hue-field wraps #panel-hues, which openPanel() writes .innerHTML
     into on every open and which is bound NON-OPTIONALLY - deleting it throws
     at script-eval and the whole app fails to boot. Same trap on
     #btn-welcome-start, which openWelcome() sets .hidden = false on with no ?..
     display:none wins over that absent attribute, so there is no ordering to
     get wrong and neither function needed changing.

   Still reachable: openIconPickerFor() (icon-failure banner route),
   openGetStartedFlow() (empty-state flow), and #welcome-x closes the sheet.
   ========================================================================== */
body.dv2-groc.a2a-clean #panel-change-icon,
body.dv2-groc.a2a-clean #panel-hue-lbl,
body.dv2-groc.a2a-clean #panel-hue-field,
body.dv2-groc.a2a-clean #btn-welcome-start,
body.dv2-groc.a2a-clean #btn-welcome-close { display: none !important; }

/* ==========================================================================
   A1-a - Grocery section menu (D1, D13, D14, D15)
   --------------------------------------------------------------------------
   #sheet-rename-section is opened by a long-press on a section header, wired to
   #shop-list (app.js) - which serves ALL EIGHT lists, Grocery, Shopping and the
   six task lists. So every rule here is gated: body.dv2-groc is the surface
   half, .a1a-clean the flag half (renderShop() sets it from
   isGroc && SECTION_MENU_V2). Flip the flag and the legacy sheet returns
   everywhere, with no other change.

   HIDE ONLY, and UI ONLY. #section-hues keeps its click handler bound and
   simply never fires; the sectionColours load/save pair and both endpoints are
   untouched, so a colour set before this shipped still round-trips. Deleting
   #section-hues or #section-delete is not an option - both are dereferenced
   NON-OPTIONALLY inside the single top-level IIFE and would take the whole app
   down at script-eval.

   Measured before writing this: on Grocery a section's stored hue already
   paints nothing. `.list .section` is transparent and `.section::before` is
   display:none for every list (style.css), and the Grocery section header is
   glass rather than a hue band (above). The sheet title was the last surface
   still showing it - hence D13.
   ========================================================================== */
.a1a-only { display: none; }
body.dv2-groc.a1a-clean .a1a-only { display: block; }
body.dv2-groc.a1a-clean #section-hue-field { display: none !important; }
/* D6/D14 - one control, two labels. The glyph is a single chevron rotated
   rather than two glyphs swapped, so there is no state in which both read. */
body.dv2-groc.a1a-clean #section-collapse.is-collapsed .a1a-fold-ic {
  transform: rotate(180deg);
}

/* ==========================================================================
   C1-b - section default store (D19, D20, D21)
   --------------------------------------------------------------------------
   Two halves, both gated: body.dv2-groc is the surface, .c1b-store the flag
   (renderShop() sets it from isGroc && SECTION_STORE_UI, which ships FALSE).
   The Store row therefore does not exist for anyone until C1-c flips the flag
   in the same commit as SECTION_STORE_DEFAULT - a picker that records a
   preference nothing acts on is worse than no picker (D19).

   The header chip is emitted by sectionHTML() rather than hidden here, because
   that template is shared with renderTrip(); the gate has to be in the branch,
   not in a stylesheet Trips also loads.
   ========================================================================== */
.c1b-only { display: none; }
body.dv2-groc.c1b-store .c1b-only { display: flex; }

/* The row carries a second line, unlike its siblings, so the label column
   stacks instead of centring. This is the only place the word "default" is
   taught - an inherited store is indistinguishable from a manual one on the
   item row (D18), so the section side carries the whole explanation. */
body.dv2-groc .lp-row .lp-sub {
  display: block;
  font-size: 11.5px;
  font-weight: 400;
  line-height: 1.3;
  margin-top: 2px;
  color: var(--dv2-text-sub, rgba(14,10,38,0.45));
}
body.dv2-groc #section-store { align-items: flex-start; }
body.dv2-groc #section-store .lp-lic { margin-top: 1px; }
body.dv2-groc #section-store .lp-lbl { flex: 1; min-width: 0; }
body.dv2-groc .lp-rval {
  font-size: 14px;
  color: var(--dv2-text-sub, rgba(14,10,38,0.45));
  margin-left: auto;
  padding-left: 8px;
  white-space: nowrap;
}

/* D20, variant A: inline after the section name. `flex: none` so the chip is
   never the thing that shrinks - a long NAME wraps, which was the measured
   trade against truncating it (variant C). */
#view-list.dv2-grocery .section-head .sec-store-chip {
  flex: none;
  margin-left: 8px;
}

/* The roll is `max-height:176px; overflow-y:auto` in style.css, sized for the
   item sheet's collapsible drop. In a sheet of its own that clipped the last
   row - "＋ Add a shop name" - half way through, which reads as a rendering
   fault rather than a scroll. A section picker is short by construction (one
   store per family shop), so let it size to its content. */
body.dv2-groc #section-store-roll,
body.dv2-groc #section-family-roll { display: block; }
body.dv2-groc #section-store-roll .shop-roll,
body.dv2-groc #section-family-roll .shop-roll {
  max-height: none;
  overflow: visible;
  width: 100%;
}
body.dv2-groc #sheet-section-store .lp-note,
body.dv2-groc #sheet-section-family .lp-note { margin-top: 12px; }

/* ==========================================================================
   A1-b - section default person (D25-D28)
   --------------------------------------------------------------------------
   Gated body.dv2-groc.a1b-fam, set by renderShop() from
   isGroc && SECTION_ASSIGNEE_UI. Unlike C1-b this ships TRUE, because the
   behaviour it records lands in the same commit - D19's rule is about the
   window between a control and its behaviour, and here there is none.

   The Family row is deliberately identical to the Store row it sits above:
   same geometry, same sub-label. They are the same kind of promise about the
   same kind of thing, and both are create-only.
   ========================================================================== */
.a1b-only { display: none; }
body.dv2-groc.a1b-fam .a1b-only { display: flex; }
body.dv2-groc #section-family { align-items: flex-start; }
body.dv2-groc #section-family .lp-lic { margin-top: 1px; }
body.dv2-groc #section-family .lp-lbl { flex: 1; min-width: 0; }

/* The header chip is an INITIAL, not a name. Two chips share this row with the
   count, the chevron and the grip at 390px, and D20 settled that the section
   name is what must not yield - so the person is a 20px disc rather than a
   word. The full name is on the title attribute and in the sheet. */
#view-list.dv2-grocery .section-head .sec-fam-chip {
  flex: none;
  margin-left: 8px;
  width: 20px; height: 20px;
  border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 10.5px; font-weight: 800; letter-spacing: 0;
  color: #fff;
  background: hsl(var(--hue, 210) 62% 52%);
}


/* ══ RECIPE SECTION ═══════════════════════════════════════════════════════
   A fixed top-level section, pinned first, always drawn — including with no
   recipes on it. Its ingredients are ordinary rows and are styled by the
   ordinary row rules; everything here is the GROUPING around them: the
   Camera / Attach / History tools, and the per-dish block.

   Scoped under `#view-list.dv2-grocery` like the rest of the file, which is
   also what makes the `--dv2-*` tokens resolve — design-v2-app.css declares
   them on that host, so a rule written outside it would void the declaration
   rather than fall back. The Recipe History SHEET lives outside #view-list and
   therefore uses no `--dv2-*` token at all; see the block after this one. */
#view-list.dv2-grocery .rcp-tools {
  display: flex; gap: 8px; flex-wrap: wrap;
  padding: 10px 12px 4px;
}
#view-list.dv2-grocery .rcp-tool {
  flex: 1 1 0; min-width: 96px;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 9px 10px;
  border-radius: 12px;
  border: .75px solid var(--dv2-g-07-bd);
  background: var(--dv2-g-10-bg);
  color: var(--dv2-text-primary);
  font: inherit; font-size: 13px; font-weight: 650; letter-spacing: .01em;
  cursor: pointer;
}
#view-list.dv2-grocery .rcp-tool:active { transform: scale(.97); }
#view-list.dv2-grocery .rcp-tool svg { flex: none; opacity: .75; }

/* One dish. The hairline on the left is the only thing saying "these rows
   belong together" once the section is scrolled and the heading is off screen,
   which is when the grouping matters most. */
#view-list.dv2-grocery .rcp-dish {
  margin: 8px 8px 4px;
  padding-left: 8px;
  border-left: 2px solid hsl(var(--hue, 210) 55% 58% / .45);
}
#view-list.dv2-grocery .rcp-dish-head {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 4px 6px;
}
#view-list.dv2-grocery .rcp-dish-name {
  margin: 0; flex: 1; min-width: 0;
  font-size: 14.5px; font-weight: 750; letter-spacing: -.01em;
  color: var(--dv2-text-primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#view-list.dv2-grocery .rcp-dish-count {
  flex: none;
  font-size: 11.5px; font-weight: 650;
  font-variant-numeric: tabular-nums;
  color: var(--dv2-text-muted);
}
#view-list.dv2-grocery .rcp-dish-btn {
  flex: none;
  padding: 4px 9px;
  border-radius: 9999px;
  border: .75px solid var(--dv2-g-07-bd);
  background: transparent;
  color: var(--dv2-text-sub);
  font: inherit; font-size: 11px; font-weight: 650;
  cursor: pointer;
}
#view-list.dv2-grocery .rcp-dish-btn.danger { color: #c2410c; }
#view-list.dv2-grocery .rcp-dish-btn:active { transform: scale(.96); }
#view-list.dv2-grocery .rcp-dish-empty,
#view-list.dv2-grocery .rcp-empty {
  padding: 10px 14px 14px;
  color: var(--dv2-text-muted);
  font-size: 13px;
}
#view-list.dv2-grocery .rcp-empty p { margin: 0 0 4px; }
#view-list.dv2-grocery .rcp-empty-sub { font-size: 12.5px; line-height: 1.45; }

/* Recipe History sheet. OUTSIDE #view-list, so deliberately no `--dv2-*`
   token appears here — `currentColor` and `color-mix` against it keep the
   sheet legible in both themes without borrowing a token that would not
   resolve. */
#sheet-recipe-history .rcp-history-body {
  display: flex; flex-direction: column; gap: 10px;
  padding: 4px 2px 8px;
}
#sheet-recipe-history .rcp-hist {
  padding: 12px 14px;
  border-radius: 14px;
  border: .75px solid color-mix(in srgb, currentColor 16%, transparent);
  background: color-mix(in srgb, currentColor 5%, transparent);
}
#sheet-recipe-history .rcp-hist-top {
  display: flex; align-items: baseline; gap: 10px;
}
#sheet-recipe-history .rcp-hist-top h3 {
  margin: 0; flex: 1; min-width: 0;
  font-size: 15px; font-weight: 700; letter-spacing: -.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#sheet-recipe-history .rcp-hist-count {
  flex: none; font-size: 11.5px; font-weight: 600; opacity: .65;
  font-variant-numeric: tabular-nums;
}
#sheet-recipe-history .rcp-hist-meta {
  display: flex; flex-wrap: wrap; gap: 4px 14px;
  margin-top: 4px;
  font-size: 12px; opacity: .7;
}
#sheet-recipe-history .rcp-hist-acts {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin-top: 10px;
}
#sheet-recipe-history .rcp-hist-link {
  font-size: 12.5px; font-weight: 650; text-decoration: none;
  color: inherit; border-bottom: 1px solid currentColor;
}
#sheet-recipe-history .rcp-hist-link.is-off { opacity: .45; border-bottom: none; }
#sheet-recipe-history .rcp-hist-btn {
  margin-left: auto;
  padding: 5px 11px;
  border-radius: 9999px;
  border: .75px solid color-mix(in srgb, currentColor 22%, transparent);
  background: transparent; color: inherit;
  font: inherit; font-size: 12px; font-weight: 650;
  cursor: pointer;
}
#sheet-recipe-history .rcp-hist-btn + .rcp-hist-btn { margin-left: 0; }
#sheet-recipe-history .rcp-hist-btn.danger { color: #c2410c; }


/* ══ GROCERY ITEM CARD · Concept C ════════════════════════════════════════
   Approved 2026-08-24. Grocery only — `.gc-row` is emitted by groceryRowHTML,
   which shopRowHTML delegates to behind isGroceryList(), so Tasks, Trips and
   non-grocery shopping lists never see any of this.

   The card is now two bands: an identity row, then a labelled metadata grid.
   The old third row — "Last bought: … Bought 9× this month" as a sentence —
   is gone; those are the two values in the grid, under their own labels, so
   they line up down a column instead of being read left to right on every row.

   112px → 90px. Everything the old card showed is still on this one.

   Scoped under #view-list.dv2-grocery like the rest of the file, which is also
   what makes the --dv2-* tokens resolve (design-v2-app.css declares them on
   that host — the C-6 trap: one of these written outside the gate voids its
   whole declaration rather than falling back). */

#view-list.dv2-grocery .row.gc-row {
  flex-direction: column;
  align-items: stretch;
  gap: 0;
  padding: 9px 14px 9px;
}
/* One tap target for the whole card. Everything interactive inside it carries
   its own data-act and wins on closest(), so this only claims the dead space. */
#view-list.dv2-grocery .gc-body {
  display: flex;
  flex-direction: column;
  gap: 7px;
  min-width: 0;
}
/* .row sets --dv2-row-line1 to align the controls to the first line box. That
   was for a single flex row; here the controls live in .gc-top, which IS the
   first line, so the compensation is not wanted and would push them down. */
#view-list.dv2-grocery .row.gc-row > * { align-self: auto; }

#view-list.dv2-grocery .gc-top {
  display: flex;
  align-items: center;
  gap: 11px;
  min-width: 0;
}
#view-list.dv2-grocery .gc-top .label-wrap { flex: 1; min-width: 0; }
#view-list.dv2-grocery .gc-top .label-row { gap: 7px; }

/* ── G2 · the basket leads ────────────────────────────────────────────────
   `.gc-top` IS the card's first line, so its first child sits on the row's
   leading edge — the position T3 reserves for the screen's single verb. On
   Grocery that verb is `select`, because the tick has moved to At the shop.

   The mark stays 28px and the 44 × 44 zone is grown with a pseudo-element, so
   the two-band layout is not disturbed by a control twice the size of the one
   it replaces. It bleeds into the card's own padding, which is what makes the
   zone the full height of the first line rather than just the glyph's box.

   The rounded square is deliberate and safe HERE only: elsewhere in PRV that
   shape means "a one-off tick that will stay" (T5), and it cannot be confused
   with one on a screen that has no tick at all. Same reasoning as the
   exemption itself — see TASK_STANDARD.md.

   `.buy-flag` (style.css:3069) supplies the dashed ring, the amber `.on` fill
   and the transition; only what the leading position changes is written here. */
#view-list.dv2-grocery .gc-top > .buy-flag.gc-lead {
  position: relative;
  margin-left: 0;
  border-radius: 9px;
}
#view-list.dv2-grocery .gc-top > .buy-flag.gc-lead::before {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  width: 44px; height: 44px;
  transform: translate(-50%, -50%);
}
/* The pop is the confirmation that the tap landed — the row tints at the same
   time, but on a pale aurora that tint is subtle and the mark is not. */
#view-list.dv2-grocery .gc-top > .buy-flag.gc-lead.on { animation: gcBasketPop .34s cubic-bezier(.2,1.5,.35,1); }
@keyframes gcBasketPop { 0% { transform: scale(1); } 42% { transform: scale(1.24); } 100% { transform: scale(1); } }
@media (prefers-reduced-motion: reduce) {
  #view-list.dv2-grocery .gc-top > .buy-flag.gc-lead.on { animation: none; }
}
/* The name is the dominant element, and this is where it earns that: one line
   to itself, nothing sharing it but its own badges. */
#view-list.dv2-grocery .gc-row .label {
  font-size: 17px;
  font-weight: 750;
  letter-spacing: -.014em;
}
#view-list.dv2-grocery .gc-row.on .label { font-weight: 700; }

/* ── The metadata grid ───────────────────────────────────────────────────
   The store column is a FIXED width, and that is the whole point of this
   layout rather than an arbitrary number. On `auto` the column sizes to each
   row's own store name, so "Tesco" and "Sainsbury's" push LAST BOUGHT to two
   different x positions and the values do not line up down the list — which is
   precisely the thing this card was redesigned to do. Fixed, every Store value
   starts at the same place and every date starts at the same place, so the eye
   reads a column instead of re-finding the field on every row. A long store
   name ellipsises inside its cell (and keeps its full text on `title`); that is
   the cost, and it is the right one, because alignment is the feature.

   96px holds "Sainsbury's" at 12.5px/650 with room to spare.

   The date column takes what is left. minmax(0,…) rather than 1fr because a
   grid track's automatic minimum is its content, which would otherwise refuse
   to shrink and push the row wide instead of truncating inside it. */
#view-list.dv2-grocery .gc-meta {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr) auto;
  gap: 1px 14px;
  align-items: end;
  padding-top: 7px;
  border-top: 1px solid var(--dv2-divider);
}
#view-list.dv2-grocery .gc-k {
  grid-row: 1;
  font-size: 8.5px;
  line-height: 1.15;
  font-weight: 750;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--dv2-text-muted);
}
#view-list.dv2-grocery .gc-v {
  grid-row: 2;
  font-size: 12.5px;
  line-height: 1.3;
  font-weight: 650;
  color: var(--dv2-text-primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
/* "Any shop" / "Never" are values, not blanks — quiet, but present. */
#view-list.dv2-grocery .gc-v.q { color: var(--dv2-text-sub); font-weight: 600; }

/* The count and History are ONE control. The number is what makes you want the
   history, so they share a target instead of sitting apart. Same
   data-act="buyhistory" the old line carried. */
#view-list.dv2-grocery .gc-hist {
  grid-row: 1 / span 2;
  align-self: center;
  display: inline-flex; align-items: center; gap: 4px;
  flex: none;
  padding: 5px 9px;
  border-radius: 9999px;
  background: var(--dv2-g-10-bg);
  border: .75px solid var(--dv2-g-07-bd);
  color: var(--dv2-link);
  font: inherit;
  font-size: 11.5px; font-weight: 750;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  cursor: pointer;
  transition: transform .14s;
}
#view-list.dv2-grocery .gc-hist:active { transform: scale(.95); }
#view-list.dv2-grocery .gc-hist svg { flex: none; opacity: .85; }

/* The transient line — "Ritesh wants this", "bought by … · Sat". Standing
   facts are in the grid; this is what is true right now. */
#view-list.dv2-grocery .meta.gc-sub { margin-top: 0; font-size: 11px; }
#view-list.dv2-grocery .row-note.gc-note { margin-top: 0; }

/* A ticked row dims its metadata with it, rather than leaving a bright grid
   under a struck-through name. */
#view-list.dv2-grocery .gc-row.on .gc-meta { opacity: .5; }

/* The basket, in the basket state. `.row.wanted` already tints the card amber
   (style.css:2759) — this only keeps the grid legible on that tint. */
#view-list.dv2-grocery .gc-row.wanted .gc-meta { border-top-color: hsl(28 60% 40% / .18); }


/* ══════════════════════════════════════════════════════════════════════════
   G3 · THE FLOATING PILL      (rooted at #view-list.dv2-grocery, as everything
   ══════════════════════════════════════════════════════════════════════════
   here is — see the file header. `.send-bar` in style.css:3093 is `position:
   sticky`, which on a 141-row pantry means scrolling to the bottom to reach the
   one control that does anything. `.gp` is FIXED to the screen instead, so it
   is in view at any scroll position, and `#shop-list` grows bottom padding so
   it can never bury the last row.

   Specificity: `#view-list.dv2-grocery .send-bar.gp` is (1,3,0) against
   style.css's (0,1,0), so nothing here needs !important. */
#view-list.dv2-grocery .send-bar.gp {
  position: fixed;
  left: 12px; right: 12px;
  bottom: calc(12px + env(safe-area-inset-bottom, 0px));
  z-index: 60;
  display: flex; align-items: center; gap: 8px;
  width: auto; margin: 0;
  padding: 8px 8px 8px 9px;
  border-radius: 22px;
  background: var(--dv2-g-10-bg);
  border: 1px solid var(--dv2-g-10-bd);
  -webkit-backdrop-filter: blur(26px) saturate(var(--dv2-g-sat));
  backdrop-filter: blur(26px) saturate(var(--dv2-g-sat));
  box-shadow: var(--dv2-g-shadow);
  animation: gpIn .38s cubic-bezier(.2,1.3,.35,1) both;
}
@keyframes gpIn { from { opacity: 0; transform: translateY(22px) scale(.94); } to { opacity: 1; transform: none; } }

/* Idled out of the way, NOT removed — one touch anywhere brings it back. */
#view-list.dv2-grocery .send-bar.gp.gp-hid {
  opacity: 0;
  transform: translateY(26px) scale(.96);
  pointer-events: none;
  animation: none;
  transition: opacity .3s ease, transform .3s cubic-bezier(.4,0,.2,1);
}

/* The last row must clear the pill at the bottom of the scroll. */
#view-list.dv2-grocery #shop-list { padding-bottom: 108px; }

#view-list.dv2-grocery .gp-count {
  flex: none; display: inline-flex; align-items: center; gap: 6px;
  height: 38px; padding: 0 11px; border-radius: 15px;
  background: hsl(28 90% 52%); border: 0; color: #fff;
  font: inherit; font-size: 14px; font-weight: 800;
  font-variant-numeric: tabular-nums; cursor: pointer;
}
#view-list.dv2-grocery .gp-count svg { width: 15px; height: 15px; }
#view-list.dv2-grocery .gp-x { opacity: .72; font-weight: 600; font-size: 15px; line-height: 1; }

#view-list.dv2-grocery .gp-stat {
  flex: 1; min-width: 0; padding-left: 6px; line-height: 1.28;
  font-size: 12px; font-weight: 650; color: var(--dv2-text-sub);
}
#view-list.dv2-grocery .gp-stat b {
  display: block; font-size: 14px; font-weight: 800; letter-spacing: -.016em;
  color: var(--dv2-text-primary); font-variant-numeric: tabular-nums;
}

#view-list.dv2-grocery .gp-ghost {
  flex: none; height: 38px; padding: 0 14px; border-radius: 15px;
  background: transparent; border: 1px solid var(--dv2-g-10-bd);
  color: var(--dv2-text-primary); cursor: pointer;
  font: inherit; font-size: 13.5px; font-weight: 750; letter-spacing: -.01em;
  white-space: nowrap;
}
#view-list.dv2-grocery .gp-primary {
  flex: 1; min-width: 0; height: 38px; padding: 0 15px; border-radius: 15px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--dv2-btn-primary); border: 0; color: #fff; cursor: pointer;
  font: inherit; font-size: 14px; font-weight: 800; letter-spacing: -.012em;
  box-shadow: var(--dv2-btn-shadow);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#view-list.dv2-grocery .gp-primary.gp-narrow,
#view-list.dv2-grocery .send-bar.gp-shop .gp-primary { flex: none; }
#view-list.dv2-grocery .gp-primary:active,
#view-list.dv2-grocery .gp-ghost:active,
#view-list.dv2-grocery .gp-count:active { transform: scale(.98); }
#view-list.dv2-grocery .gp-primary:disabled { opacity: .6; }


/* ══════════════════════════════════════════════════════════════════════════
   G4 · AT THE SHOP
   ══════════════════════════════════════════════════════════════════════════
   Everything the resting list carries is hidden rather than unbuilt: the same
   #view-list, the same #shop-list, one class. Leaving the screen restores it
   without a rebuild, which is what makes stepping out and back cheap. */
#view-list.dv2-grocery.gshop .lp-tools,
#view-list.dv2-grocery.gshop #lp-kind-seg,
#view-list.dv2-grocery.gshop .overall,
#view-list.dv2-grocery.gshop #group-tabs,
#view-list.dv2-grocery.gshop .fab-wrap,
#view-list.dv2-grocery.gshop #shop-icon-warn { display: none; }

#view-list.dv2-grocery.gshop #shop-list { padding: 14px 14px 118px; }

#view-list.dv2-grocery .gs-prog {
  padding: 15px 16px 16px; border-radius: 18px; margin-bottom: 16px;
  background: var(--dv2-g-10-bg); border: 1px solid var(--dv2-g-10-bd);
  -webkit-backdrop-filter: blur(20px) saturate(var(--dv2-g-sat));
  backdrop-filter: blur(20px) saturate(var(--dv2-g-sat));
  box-shadow: var(--dv2-g-shadow);
}
#view-list.dv2-grocery .gs-prog-hd { display: flex; align-items: baseline; gap: 7px; margin-bottom: 11px; }
#view-list.dv2-grocery .gs-prog-big {
  font-size: 25px; font-weight: 800; letter-spacing: -.03em;
  color: var(--dv2-text-primary); font-variant-numeric: tabular-nums;
}
#view-list.dv2-grocery .gs-prog-sm { font-size: 13px; font-weight: 700; color: var(--dv2-text-sub); }
#view-list.dv2-grocery .gs-bar {
  height: 7px; border-radius: 99px; background: var(--dv2-progress-bg); overflow: hidden;
}
#view-list.dv2-grocery .gs-bar i {
  display: block; height: 100%; border-radius: 99px;
  background: var(--dv2-progress-fill);
  transition: width .42s cubic-bezier(.3,.9,.3,1);
}

#view-list.dv2-grocery .gs-bigs { display: flex; flex-direction: column; gap: 9px; }

/* One big target. Label and tick, plus the section — a grocery trip is walked
   in aisle order, which is the one thing Shop's own big row has no need of. */
#view-list.dv2-grocery .gs-big {
  display: flex; align-items: center; gap: 15px; width: 100%;
  padding: 16px 17px; border-radius: 18px; cursor: pointer; text-align: left;
  background: var(--dv2-groc-card-bg); border: 1px solid var(--dv2-groc-card-bd);
  color: var(--dv2-text-primary); box-shadow: 0 1px 3px rgba(20,20,40,.05);
  -webkit-backdrop-filter: blur(18px) saturate(var(--dv2-g-sat));
  backdrop-filter: blur(18px) saturate(var(--dv2-g-sat));
  font: inherit;
  transition: background .2s, border-color .2s, transform .12s;
}
#view-list.dv2-grocery .gs-big:active { transform: scale(.99); }
#view-list.dv2-grocery .gs-box {
  flex: none; width: 30px; height: 30px; border-radius: 9px;
  display: grid; place-items: center;
  border: 2px solid var(--dv2-text-muted); color: transparent;
  transition: background .2s, border-color .2s, color .2s;
}
#view-list.dv2-grocery .gs-box svg path { stroke-dasharray: 26; stroke-dashoffset: 26; }
#view-list.dv2-grocery .gs-big.on {
  background: color-mix(in srgb, var(--dv2-accent) 12%, var(--dv2-groc-card-bg));
  border-color: color-mix(in srgb, var(--dv2-accent) 34%, transparent);
}
#view-list.dv2-grocery .gs-big.on .gs-box {
  background: var(--dv2-accent); border-color: var(--dv2-accent); color: #fff;
  animation: gsPop .4s cubic-bezier(.2,1.5,.35,1);
}
#view-list.dv2-grocery .gs-big.on .gs-box svg path { animation: gsDraw .34s cubic-bezier(.4,0,.2,1) forwards; }
@keyframes gsPop  { 0% { transform: scale(1); } 45% { transform: scale(1.22); } 100% { transform: scale(1); } }
@keyframes gsDraw { from { stroke-dashoffset: 26; } to { stroke-dashoffset: 0; } }

#view-list.dv2-grocery .gs-lbl { flex: 1; min-width: 0; font-size: 19px; font-weight: 750; letter-spacing: -.02em; }
#view-list.dv2-grocery .gs-big.on .gs-lbl {
  opacity: .5; text-decoration: line-through; text-decoration-thickness: 1.5px;
}
#view-list.dv2-grocery .gs-sec {
  flex: none; font-size: 10px; font-weight: 800; letter-spacing: .08em;
  text-transform: uppercase; color: var(--dv2-text-muted);
}
#view-list.dv2-grocery .gs-awake {
  text-align: center; font-size: 11.5px; font-weight: 650;
  color: var(--dv2-text-muted); padding: 18px 0 2px; margin: 0;
}

@media (prefers-reduced-motion: reduce) {
  #view-list.dv2-grocery .send-bar.gp { animation: none; }
  #view-list.dv2-grocery .gs-big.on .gs-box { animation: none; }
  #view-list.dv2-grocery .gs-big.on .gs-box svg path { animation: none; stroke-dashoffset: 0; }
}


/* ══════════════════════════════════════════════════════════════════════════
   G1 · FAVOURITES
   ══════════════════════════════════════════════════════════════════════════
   A fixed second tab, not a filter chip: Favourites is a place you go, and it
   sits in the header where the list's identity is, not in the toolbar where
   its verbs are. */
#view-list.dv2-grocery .gf-tabs {
  display: flex; gap: 4px;
  padding: 2px 0 0;
  border-bottom: 1px solid var(--dv2-divider);
}
#view-list.dv2-grocery .gf-tab {
  position: relative; flex: none;
  border: 0; background: transparent; cursor: pointer;
  font: inherit; font-size: 13.5px; font-weight: 750; letter-spacing: -.012em;
  color: var(--dv2-text-muted);
  padding: 7px 12px 10px;
}
#view-list.dv2-grocery .gf-tab.on { color: var(--dv2-text-primary); }
#view-list.dv2-grocery .gf-tab.on::after {
  content: ""; position: absolute; left: 12px; right: 12px; bottom: 2px;
  height: 2.5px; border-radius: 2px; background: var(--dv2-accent);
}

#view-list.dv2-grocery .gf-note {
  padding: 11px 13px; margin-bottom: 12px; border-radius: 13px;
  background: var(--dv2-g-10-bg); border: 1px solid var(--dv2-g-07-bd);
  font-size: 11.5px; font-weight: 650; color: var(--dv2-text-sub); line-height: 1.45;
}
#view-list.dv2-grocery .gf-note b {
  color: var(--dv2-text-primary); font-weight: 800; font-variant-numeric: tabular-nums;
}
#view-list.dv2-grocery .gf-rows { display: flex; flex-direction: column; gap: 8px; }
#view-list.dv2-grocery .gf-next {
  padding: 16px 6px 4px; margin: 0; text-align: center;
  font-size: 11.5px; font-weight: 600; color: var(--dv2-text-muted);
}

/* The empty state EXPLAINS the rule rather than asserting it — a blank tab
   with "no favourites" tells you nothing about how to get one. */
#view-list.dv2-grocery .gf-empty {
  padding: 46px 22px; text-align: center;
  display: flex; flex-direction: column; gap: 9px; align-items: center;
}
#view-list.dv2-grocery .gf-empty-ring {
  width: 54px; height: 54px; border-radius: 50%; display: grid; place-items: center;
  border: 1.6px dashed var(--dv2-text-muted); color: var(--dv2-text-muted);
}
#view-list.dv2-grocery .gf-empty-ring svg { width: 22px; height: 22px; }
#view-list.dv2-grocery .gf-empty h3 {
  margin: 4px 0 0; font-size: 17px; font-weight: 800;
  letter-spacing: -.02em; color: var(--dv2-text-primary);
}
#view-list.dv2-grocery .gf-empty p {
  margin: 0; max-width: 32ch; line-height: 1.5;
  font-size: 13px; font-weight: 600; color: var(--dv2-text-sub);
}

/* The item's home section, under its name. Favourites only — groceryRowHTML
   emits it on request, and the sections' own rows never ask. */
#view-list.dv2-grocery .gc-home {
  display: block; margin-top: 1px;
  font-size: 10.5px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--dv2-text-muted);
}
