/* ─────────────────────────────────────────────────────────────────────────────
 * Metatron Mind — STOREFRONT shell + view layouts.  "Bone Terminal"
 *
 * OWNER: shell-ui (task-2).  Consumes tokens.css (design-system) — loaded FIRST.
 * AUTHORITY: docs/STOREFRONT-DESIGN.md §3 space/shape/motion, §4 primitives,
 *            §5 mount points, §6 behaviour.  The order desk's own stylesheet is
 *            never loaded by the storefront (nor touched by this redesign).
 *
 * ── DIVISION OF LABOUR (written into tokens.css by design-system) ───────────
 *   tokens.css = every :root token + the VISUAL TREATMENT of the four §4
 *   primitives (.plate, .spec-row, .chip, .drawer family) + base defaults at
 *   :where() specificity 0 so this file always wins.
 *   THIS FILE = the shell (skip link, sticky #nav, #ticker guard, footer), the
 *   #view container, every #storefront[data-view] layout, the loading and empty
 *   states, the grid/detail/cart composition, and the two surface deltas the
 *   Lead assigned here:
 *     1. the SPECIMEN-PLATE white-mockup treatment (print mount + board
 *        vignette) — every live mockup is an OPAQUE pure-white 1400×1400 raster,
 *        measured, so the white is curated instead of hidden;
 *     2. ≥44×44 touch targets for .chip in the shell regardless of pointer type.
 *   Nothing here re-implements a tokens.css primitive — only deltas.
 *
 * ── MARKUP THIS STYLES (as actually emitted by store.js, read from source) ──
 *   grid   : #grid > #gridHead.grid-head (.eyebrow,h1.view-title,p.lede,dl.stats
 *            > .stat>dt+dd) · #hero.hero > .hero-art>.plate + .hero-copy
 *            (.eyebrow,h2.hero-title,p.lede,.size-chips>button.size-chip,
 *             a.btn.btn-primary,p.fine) · #facetBar.facets > .chips>.chip>.chip-n
 *            + label.field.field-search>.field-label+input#search
 *            + label.field.field-sort>.field-label+select#sort
 *            + p.result-count#gridCount · #gridBody.tiles > article.tile
 *            [data-add-id] > a.tile-link > (.plate>.plate-art>img.plate-img
 *            +.plate-rail) + .tile-body(h2.tile-title,p.tile-tagline,
 *            p.tile-price>.from, optional .spec-row) then .tile-sizes>button.size-chip
 *            · #gridEmpty.empty>.eyebrow+h2.empty-title+p.lede+p.fine
 *   detail : #detail.detail > nav.crumbs>a · .detail-grid > #detailArt.gallery
 *            > figure.plate.plate-lg(.plate-art+figcaption.plate-rail)
 *            + .gallery-thumbs>a.thumb>img · .detail-copy(.eyebrow,h1.view-title,
 *            p.lede,p.price-lg,#detailFacts.specs>spec rows,#detailBuy.buy
 *            >fieldset.sizes>legend.field-label+select#detailSize.size-select,
 *            p.buy-price,button#addToCart.btn.btn-primary.btn-block,
 *            p.fine.buy-hint,p.transit,p.ddp,.warn-blank)
 *            · section.detail-desc>h2+.prose>p,ul.bullets,.specs,p.fine.care-note
 *   cart   : .cart-page > header.cart-page-head(.eyebrow,h1.view-title,p.lede)
 *            + cart.js's .cart-lines/.cart-line/.cart-sum/.sum-row/.ship-group
 *   404    : #detail.detail > nav.crumbs + .detail-copy (h1.view-title)
 *   drawer : #drawer > .drawer-head(#drawerTitle,#drawerClose) + #drawerBody
 *            + #drawerSummary.drawer-foot   (tokens.css paints the shell)
 *   motion : #toast (geometry only — motion.js owns opacity/transform and
 *            [data-mm-show]); #ticker (motion.js owns it entirely);
 *            .mm-armed/.mm-in are never authored here (default DOM is visible).
 * ──────────────────────────────────────────────────────────────────────────── */

/* Local shell vars — prefixed so they can never collide with a locked token. */
:root {
  --sf-gutter: clamp(16px, 4vw, 40px);   /* page rail, mobile-first */
  --sf-rail: 34px;                       /* caption-rail height (matches tokens) */
}

/* ═══ 0 · base ══════════════════════════════════════════════════════════════ */

*, *::before, *::after { box-sizing: border-box; }

html {
  background: var(--void);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--void);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  overflow-wrap: break-word;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, p, figure, blockquote, dl, dd, ul, ol { margin: 0; }
ul, ol { padding: 0; list-style: none; }
img, svg, video { max-width: 100%; }
/* `height: auto` is the right default for prose images, but it CONTRADICTED the plate rule in
   tokens.css (`.plate-art img { height: 100% }`), which is the one place an image must be measured
   against its frame rather than its own aspect ratio. Specificity means the plate rule wins, so
   this was latent rather than active — but this file loads LAST, and a contradictory pair of rules
   one cascade step from misbehaving is not worth keeping. The plate owns its own images. */
img { height: auto; border: 0; }
.plate-art img, .plate-art svg { height: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }
a { text-decoration: none; }

/* Focus: --cyan 2px on a 2px --void offset (tokens --focus-w/--focus-off), so it
   reads on the dark ground AND on a bone plate. The box-shadow fills the offset
   gap with --void; on bone that dark ring is what makes the cyan visible. */
:focus-visible {
  outline: var(--focus-w) solid var(--cyan);
  outline-offset: var(--focus-off);
  box-shadow: 0 0 0 var(--focus-off) var(--void);
  border-radius: var(--r-plate);
}

/* ── shared type atoms used across views ──────────────────────────────────── */

.view-title,
.display-hero {
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  font-stretch: var(--wd-display);
  font-size: var(--t-h1);
  line-height: var(--lh-h1);
  letter-spacing: var(--tr-h1);
  text-transform: uppercase;
  text-wrap: balance;
  color: var(--text);
}
.lede {
  max-width: var(--maxw-prose);
  font-size: var(--t-sm);
  line-height: var(--lh-sm);
  color: var(--text-2);
}
.lede + .lede { margin-top: var(--s-3); }
.fine {
  max-width: var(--maxw-prose);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  line-height: 1.7;
  letter-spacing: 0.01em;
  color: var(--text-3);
}

/* store.js writes the [BRACKET] motif as literal text ("[ ARCHIVE ]") while
   tokens.css draws a bracket pair via ::before/::after. Both together render
   "[[ ARCHIVE ]]". The shell neutralises the CSS pair here; the real fix belongs
   in one of those two files (reported to the Lead). designsystem.html, which
   writes plain text, is unaffected because this is scoped to #storefront. */
#storefront .eyebrow::before,
#storefront .eyebrow::after { content: none; }

/* Content links keep the cyan signal (tokens sets `:where(#storefront) a`); the
   shell's chrome links opt out explicitly further down. `:not(.btn)` matters:
   without it this rule beats .btn-primary and paints a cyan label onto a cyan
   button (measured defect in the empty-cart CTA). */
.prose a:not(.btn), .lede a:not(.btn), .fine a:not(.btn), .crumbs a:not(.btn),
.empty a:not(.btn), .noscript a:not(.btn), .foot a:not(.btn) {
  color: var(--cyan);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}
.prose a:not(.btn):hover, .lede a:not(.btn):hover, .fine a:not(.btn):hover,
.crumbs a:not(.btn):hover, .empty a:not(.btn):hover { color: var(--text); }

/* ═══ 1 · shell: skip link, sticky nav, ticker, footer ══════════════════════ */

.skip {
  position: fixed;
  top: var(--s-2);
  left: var(--s-2);
  z-index: 400;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--s-4);
  transform: translateY(-180%);
  background: var(--cyan);
  color: var(--void);
  border-radius: var(--r-plate);
  font-family: var(--font-mono);
  font-size: var(--t-data);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  transition: transform var(--dur-fast) var(--ease-out-expo);
}
.skip:focus { transform: none; }

#storefront {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
}

.nav {
  position: sticky;
  top: 0;
  z-index: var(--z-nav);
  background: var(--ink);
  border-bottom: var(--hair) solid var(--line);
}
/* the ONE rationed glitch element on the page (§1): a 1px signature rule */
.nav::after {
  content: "";
  display: block;
  height: var(--hair);
  background: var(--glitch);
}
.nav-in {
  display: flex;
  align-items: center;
  /* The wordmark is operator copy (identity.name, capped at 90 chars). If a long name cannot
     share the row, it takes a row of its own instead of squeezing the nav or pushing the cart
     button off-screen; with the shipped name nothing wraps and the row is unchanged. */
  flex-wrap: wrap;
  gap: var(--s-3);
  width: 100%;
  max-width: calc(var(--maxw) + 2 * var(--sf-gutter));
  margin-inline: auto;
  min-height: 56px;
  padding: var(--s-2) var(--sf-gutter);
}

/* The wordmark is patched from the operator's store name AFTER the payload lands
   (`wm-1`/`wm-2`), so its height must not depend on how long that name is, or the
   whole page below the header shifts when it arrives — measured 0.0804 at 320 and
   0.0930 at 390 with a 90-character name, against 0.0000 for the shipped 13-char
   one. A wrapping wordmark grows the nav (the phase-2 attempt set white-space:normal
   and min-width:0, which lets it wrap to 4-5 lines); instead the mark is bounded and
   clamped to ONE line with an ellipsis, so the nav's height is set by the 44px cart
   button and never by the name. Nothing is removed from the document: the full name
   still renders as the grid/detail h1 and in the detail crumb, and the mark keeps its
   two-tone wm-1/wm-2 treatment. */
.wordmark {
  display: block;
  min-width: 0;
  max-width: min(46vw, 22rem);
  min-height: 44px;
  padding-block: var(--s-2);
  color: var(--text);
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  font-size: 0.9375rem;
  line-height: 1;
  letter-spacing: var(--tr-h3);
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.wm-2 { margin-left: 0.45ch; color: var(--text-2); }
.wordmark:hover .wm-2 { color: var(--cyan); }

.nav-links {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: var(--s-2);
  margin-left: auto;
}
.nav-link {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--s-2);
  color: var(--text-2);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
  border-bottom: var(--hair) solid transparent;
}
.nav-link:hover { color: var(--text); }
.nav-link[aria-current="page"] { color: var(--cyan); border-bottom-color: var(--cyan); }

.cart-btn {
  position: relative;
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-width: 44px;
  min-height: 44px;
  padding: 0 var(--s-3);
  color: var(--text);
  border: var(--hair) solid var(--line-2);
  border-radius: var(--r-pill);
}
.cart-btn:hover { color: var(--cyan); border-color: var(--cyan-dim); }
.cart-count {
  display: inline-grid;
  place-items: center;
  min-width: 20px;
  height: 20px;
  padding: 0 5px;
  background: var(--cyan);
  color: var(--void);
  border-radius: var(--r-pill);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

/* motion.js fills #ticker and removes it under reduced motion. A marquee must
   never be able to widen the document, so the guard lives here.
   ── HEIGHT IS RESERVED FROM THE FIRST PAINT, AT EVERY WIDTH ────────────────
   The markup used to carry the boolean `hidden` attribute, i.e. display:none, so
   the band reserved no height; when motion.js revealed the populated band 20px
   appeared above #view and pushed it down. Measured at 390/844, throttled: one
   layout-shift entry, v=0.02151, source #view — the entire CLS budget.
   The height below reproduces motion.js's `#ticker[data-mm-ticker]` box from tokens
   rather than hard-coding a pixel value: 2 × --hair border + one .ticker-item line
   box (--t-label × 1.6) = 19.6px, which is the measured populated height 19.5938px.
   It is FIXED, not min-height: min-height re-grows if the content ever exceeds it,
   and overflow:hidden clips instead — motion-polish measured both variants.
   Font-independent (no text in the band) and content-independent, so a font swap or
   a longer ticker string cannot reflow the page. Verified CLS 0.000 with 0 shift
   entries at 390 and 1440 under Slow-4G + 4x CPU; under prefers-reduced-motion
   tokens.css sets #ticker{display:none !important} so no space is reserved. */
#ticker {
  max-width: 100%;
  overflow: hidden;
  height: calc(2 * var(--hair) + 1.6 * var(--t-label));
  border-block: var(--hair) solid var(--line);
  background: var(--ink);
}

.view {
  flex: 1 0 auto;
  width: 100%;
  margin-inline: auto;
  padding: var(--s-6) var(--sf-gutter) var(--s-9);
}
.view:focus { outline: none; }
#view > * { animation: sf-in var(--dur) var(--ease-out-expo) both; }
@keyframes sf-in { from { opacity: 0; } to { opacity: 1; } }

.foot {
  border-top: var(--hair) solid var(--line);
  background: var(--ink);
  padding: var(--s-8) var(--sf-gutter) var(--s-9);
}
.foot-in {
  display: grid;
  gap: var(--s-5);
  max-width: var(--maxw);
  margin-inline: auto;
}
/* the ONE gold element per viewport (§1: earned emphasis only) */
.foot-mark {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  font-weight: 700;
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--gold);
}
.foot-line {
  max-width: var(--maxw-prose);
  font-size: var(--t-h3);
  line-height: 1.35;
  letter-spacing: var(--tr-h3);
  color: var(--text);
}
.foot-facts {
  display: grid;
  gap: var(--s-4) var(--s-6);
  grid-template-columns: 1fr;
  padding-top: var(--s-5);
  border-top: var(--hair) solid var(--line);
}
.foot-facts dt {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-3);
}
.foot-facts dd {
  font-family: var(--font-mono);
  font-size: var(--t-data);
  font-variant-numeric: tabular-nums;
  color: var(--text-2);
}
.foot-fine {
  max-width: var(--maxw-prose);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  line-height: 1.7;
  color: var(--text-3);
}

/* ── PROOF band — the no-analytics position stated as data ──────────────────
 * Sits in the footer, which is inside #storefront and outside #view, so it renders
 * on the grid, detail, cart, empty and 404 views with no JS. Register is the house
 * spec sheet: a [BRACKET] eyebrow, a hairline rule and mono tabular marks — NO
 * badge, shield, tick, pill or green chrome, which is what a fake trust badge looks
 * like, and no second gradient (the ticker owns the page's one rationed gradient).
 * Contrast on --ink: marks --text-2 (measured 8.16:1), sub --text-2, headline
 * --text (16.2:1), eyebrow --cyan (11.6:1), fine print --text-3 (5.56:1). The rules
 * are --line, the same decorative hairline as every other rule in the design (see
 * tokens.css: "--line: hairline on dark — decorative"); brightening it here alone
 * would break the system for no legibility gain, since no content depends on it. */
.proof {
  display: grid;
  gap: var(--s-3);
  margin-top: var(--s-6);
  padding-top: var(--s-6);
  border-top: var(--hair) solid var(--line);
}
.proof .eyebrow { justify-self: start; }
.proof-title {
  font-family: var(--font-sans);
  font-weight: var(--fw-display);
  font-size: clamp(1.25rem, 2.4vw, 1.75rem);
  line-height: var(--lh-h2);
  letter-spacing: var(--tr-h2);
  text-transform: uppercase;
  text-wrap: balance;
  color: var(--text);
}
.proof-sub {
  max-width: var(--maxw-prose);
  font-size: var(--t-sm);
  line-height: var(--lh-sm);
  color: var(--text-2);
}
.proof-marks {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
  margin: var(--s-2) 0 0;
}
.proof-mark {
  padding: var(--s-3) 0;
  border-top: var(--hair) solid var(--line);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  color: var(--text-2);
}
/* the marks stay one mono line per cell; if space is tight they stack rather than
   shrink, and the existing footer facts are never reduced to make room. */
@media (min-width: 560px) {
  .proof-marks { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1000px) {
  .proof-marks { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* ═══ 2 · view states — layout keyed off #storefront[data-view] ════════════ */

/* ── per-view geometry ──────────────────────────────────────────────────────
 * #view's OWN BOX must not change between views: a box that changes width or
 * height is itself an unstable element, and Chrome attributes a layout shift to it
 * (measured: detail/cart 0.0503/0.0565 at 1440, and the bulk of empty/404's 0.1884,
 * purely from #view going 1440px wide -> 1280/1260/840 as data-view flips). So the
 * box keeps ONE width, and the narrower reading measure moves to the view's own
 * root element, which is a *child* — children may change freely.
 * min-height comes from --sf-reserve, the boot skeleton's measured height, set by
 * the inline script in index.html; it must not be overridden per view or the boot
 * frame and the rendered frame disagree again. */
#storefront #view {
  max-width: calc(var(--maxw) + 2 * var(--sf-gutter));
  min-height: var(--sf-reserve, 0px);
}
#storefront[data-view="detail"] #view > * { max-width: calc(1200px + 2 * var(--sf-gutter)); margin-inline: auto; }
#storefront[data-view="cart"]   #view > * { max-width: calc(1180px + 2 * var(--sf-gutter)); margin-inline: auto; }
#storefront[data-view="empty"]  #view,
#storefront[data-view="404"]    #view {
  display: grid;
  align-content: center;
}
#storefront[data-view="empty"] #view > *,
#storefront[data-view="404"]   #view > * { max-width: calc(760px + 2 * var(--sf-gutter)); margin-inline: auto; }

/* ═══ 3 · loading state — the grid's own geometry, painted as pending ══════
 * The skeleton reuses the real classes (.grid-head/.stats/.hero/.facets/.tiles/
 * .plate/.tile-body/.size-chip), so the grid template, the plate aspect-ratio and
 * every media query apply unchanged and the first screenful lands on the same
 * pixels as the payload that replaces it. A skeleton of a different height is a
 * layout shift when the data arrives — measured previously as a #view/FOOTER
 * shift; see the ticker note above for the other half of the same budget. */

.boot { display: block; }
.boot-title { color: var(--text); }

/* one exposed status line for assistive tech; the visual skeleton is decorative */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* the pending plate keeps the real plate's boxes and drops only the print mount */
.plate.is-sk {
  background: var(--panel);
  border-color: var(--line);
  box-shadow: none;
}
.plate.is-sk:hover { border-color: var(--line); }
/* the pending plate keeps the real plate's structure: --panel mount ring + a
   --panel-2 print area, painted by background-clip on the SAME 6% padding box, so
   it reads as a print that has not arrived yet rather than a flat slab — and adds
   no element, no height and no layout. */
.plate.is-sk .plate-art {
  background-color: var(--panel);
  background-image: linear-gradient(var(--panel-2), var(--panel-2));
  background-clip: content-box, border-box;
}
.plate.is-sk .plate-art::after { content: none; }
.plate.is-sk .plate-rail { background: var(--panel-2); border-top-color: var(--line); }

.boot .plate.is-sk { animation: sf-breathe 1.6s var(--ease) infinite; }
@keyframes sf-breathe { 0%, 100% { opacity: 0.62; } 50% { opacity: 1; } }

/* ═══ 4 · §4 primitive DELTAS owned by this file ═══════════════════════════ */

/* ── 4.1 Specimen plate: the white-mockup treatment ────────────────────────
 * Measured: all 7 live mockups are OPAQUE pure-white 1400×1400 RGB rasters.
 * "Full Circle Sexy" (WT0166) is a WHITE crop top whose body measures #F0EFED —
 * only ~6% below the white field it sits on, so on a flat field it dissolves.
 * The product art is NEVER filtered, shadowed or darkened; the separation is
 * manufactured entirely by the PLATE:
 *   · the board is a graded bone (light top → darker corners), never flat;
 *   · the white print is a bounded sheet: a 1px --plate-line edge plus a
 *     2px/8px shadow that falls on the BOARD, so the print reads as mounted;
 *   · the mount ring around the print (6%, per §4) is what carries the tone, so
 *     a white garment is framed by a visibly darker board on every side.
 * A darkening vignette drawn OVER the print was rejected: it reduces the white
 * garment's own 6% edge contrast and is the one thing that would touch the art. */
.plate {
  background:
    radial-gradient(132% 118% at 50% 34%, #F8F5EF 0%, var(--plate) 54%, #E5E0D3 100%);
  box-shadow: inset 0 1px 22px rgba(18, 24, 29, 0.06);
}
.plate-art { position: relative; background: radial-gradient(126% 126% at 50% 42%, var(--plate) 38%, #DBD3C2 100%); }
.plate-art::after {
  content: "";
  position: absolute;
  inset: 6%;                     /* exactly the art's 6% padding = the print box */
  z-index: 1;
  pointer-events: none;
  /* the print edge + the shadow it casts on the board */
  box-shadow: 0 0 0 var(--hair) var(--plate-line), 0 2px 10px rgba(18, 24, 29, 0.13);
}
/* the plate answers the whole tile link, not only the art, so hovering the title
   lights the plate too. The cue is the border colour; the art does NOT resize — see the
   rationale in tokens.css. These rules used to repeat tokens.css's scale(1.02), which is why
   the zoom was specified in two files and a fallback plate could still grow. */
.tile-link:hover .plate,
.tile-link:focus-visible .plate { border-color: var(--cyan); }
/* Kept as an explicit guard, not because anything scales today: commerce-behavior's onerror
   placeholder (an <img> swapped to a data-URI plate with the real title/GSM/price) is a notice,
   not a photograph, and must not be animated as one if a transform is ever reintroduced. */
.plate:hover .plate-art img.is-fallback,
.plate:focus-within .plate-art img.is-fallback,
.tile-link:hover .plate-art img.is-fallback { transform: none; }
.plate-lg { --plate-ratio: 1 / 1; }

/* Caption rail: tokens.css gives the band (fixed 34px, --plate-2, mono). The
   shell adds the slot colours for the span names store.js actually emits —
   piece · GSM · oz, plus an optional badge — and a rail that can never blow the
   document width open. Real facts only; facts.styleCode is never rendered. */
.plate-rail { gap: var(--s-1); letter-spacing: 0.05em; overflow: clip; }
.plate-rail > * { flex: 0 1 auto; min-width: 0; }
.plate-rail .rail-piece { color: var(--ink-text); font-weight: 600; letter-spacing: 0.1em; }
.plate-rail .rail-gsm { color: var(--ink-text); font-weight: 600; }
.plate-rail .rail-oz { color: var(--ink-text-2); }
/* separator: --ink-text-2 measures 4.56:1 on --plate-2 and 5.18:1 on --plate, so
   it clears the 4.5:1 text and 3:1 non-text minimums. --plate-edge was 2.92:1
   (manual measurement; axe is silent on punctuation). commerce-behavior marks the
   dots aria-hidden="true", so they are decorative either way. */
.plate-rail .rail-dot { color: var(--ink-text-2); }
.plate-rail .rail-badge {
  order: -1;
  margin-right: var(--s-1);
  padding: 1px var(--s-2);
  /* same finding as .rail-dot: on the --plate-2 rail --plate-edge measures 2.92:1,
     so the pill's edge uses --ink-text-2 (4.56:1) like the rest of the rail type. */
  border: var(--hair) solid var(--ink-text-2);
  border-radius: var(--r-pill);
  color: var(--ink-text);
}
.plate-rail .rail-price { margin-left: auto; color: var(--ink-text); font-weight: 700; }

/* ── 4.3 Data chip + the add-to-cart size picker ──────────────────────────
 * tokens.css owns .chip (including its unconditional 44px minimum) and .size-chip
 * shares that vocabulary. BUT store.js emits the size pickers as
 * `<button class="size-chip">` with NO .chip class, so they received none of it:
 * measured on the real page before this rule — border 0 none, background
 * transparent, border-radius 0, display block, only my 44×44 target box left. The
 * add-to-cart control rendered as loose text. These declarations are a deliberate
 * exact mirror of tokens' .chip so that a size chip and a facet chip are the same
 * object visually; if commerce-behavior ever adds `chip` to the class list, the two
 * paths agree value-for-value and nothing changes. (The size pickers are always on
 * the dark ground — .tile-sizes/.size-chips are siblings of .plate, never inside it.) */
.size-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0 var(--s-3);
  background: var(--panel);
  color: var(--text-2);
  border: var(--hair) solid var(--line-2);
  border-radius: var(--r-pill);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.size-chip:hover { color: var(--text); border-color: var(--cyan); }
.size-chip.is-on { background: var(--cyan); border-color: var(--cyan); color: var(--void); }
.size-chip:disabled { color: var(--text-3); border-color: var(--line); background: transparent; cursor: not-allowed; }

/* ── 4.4 Drawer: safety net only. tokens.css owns the shape and the [data-open]
 * / .is-open states; if a consumer merely clears the `hidden` attribute the
 * drawer must still open rather than stay parked off-screen. */
#drawer:not([hidden]) { transform: none; visibility: visible; transition-delay: 0s; }
#drawer.is-open, #drawer.open, #drawer[data-open], #drawer[aria-hidden="false"] { transform: none; visibility: visible; }
#drawer:not([hidden]) ~ .scrim,
#drawer.is-open ~ .scrim,
#drawer[data-open] ~ .scrim { opacity: 1; visibility: visible; transition-delay: 0s; }
/* drawer internals: the cart lines are denser in the 420px column, and the
   summary may never squeeze the lines out of sight — with the shipping form open
   it is ~700px tall, which would leave the body ~140px. Cap it and let it scroll
   inside itself; the checkout button stays reachable at the end of that scroll. */
#drawerBody { display: grid; gap: var(--s-4); align-content: start; min-height: 28%; }
#drawerSummary { max-height: min(62%, 560px); overflow-y: auto; overscroll-behavior: contain; }
#drawerSummary .specs .spec-row { padding-block: var(--s-1); }
#drawerSummary .ship-form { gap: var(--s-2); }
#drawerSummary .ship-opt { min-height: 44px; }
#drawerBody .line,
#drawerBody .cart-line { grid-template-columns: 64px minmax(0, 1fr); padding: var(--s-3); }
#drawerBody .line-title,
#drawerBody .cart-line-title,
#drawerBody .line-body h2,
#drawerBody .cart-line-main h2 { font-size: var(--t-data); }
#drawerSummary:empty { display: none; }

/* ═══ 5 · grid view ═════════════════════════════════════════════════════════ */

.grid-head { display: grid; gap: var(--s-3); align-content: start; }
.grid-head .lede { margin-top: var(--s-1); }

/* the spec sheet as hero typography: real numbers, mono, tabular, hairline-ruled */
.stats {
  display: flex;
  flex-wrap: wrap;
  margin-top: var(--s-5);
  border-top: var(--hair) solid var(--line);
  border-bottom: var(--hair) solid var(--line);
}
.stat {
  flex: 1 1 auto;
  min-width: 132px;
  padding: var(--s-3) var(--s-4) var(--s-3) 0;
}
.stat + .stat { padding-left: var(--s-4); border-left: var(--hair) solid var(--line); }
.stat dt {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-3);
}
.stat dd {
  margin: 0;
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  font-stretch: var(--wd-display);
  font-size: clamp(1.0625rem, 2.2vw, 1.4375rem);
  line-height: 1.15;
  letter-spacing: var(--tr-h3);
  font-variant-numeric: tabular-nums;
  text-transform: uppercase;
  color: var(--text);
}

/* ── featured listing ─────────────────────────────────────────────────────── */
.hero {
  display: grid;
  gap: var(--s-6);
  margin-top: var(--s-7);
  padding-top: var(--s-6);
  border-top: var(--hair) solid var(--line);
}
.hero-art { min-width: 0; }
.hero-copy {
  display: grid;
  gap: var(--s-4);
  align-content: center;
  min-width: 0;
}
.hero-title {
  font-family: var(--font-sans);
  font-weight: var(--fw-display);
  font-size: var(--t-h2);
  line-height: var(--lh-h2);
  letter-spacing: var(--tr-h2);
  text-transform: uppercase;
  color: var(--text);
}
.hero-copy .btn { justify-self: start; }

/* ── store hero — OPT-IN store-level copy (CONTENT-LAYER.md §4.6) ────────────
 * Rendered by store.js ABOVE #gridHead, and only when hero.enabled === true AND
 * hero.headline is non-empty, so the shipped page — and any empty vault — shows no
 * section here at all. Deliberately single-columned: there is no product art to
 * pair with store-level copy (the featured plate is the separate #hero mount
 * further down), and the layout must not change when the operator turns it on. */
.store-hero {
  display: grid;
  /* minmax(0,1fr), not the implicit auto track: an auto track is sized by its items' max-content,
     so one capped 160-char headline line blew the section (and the page) out to ~500px at 320px.
     A zero-based track clamps the column to the container and lets the copy wrap. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-4);
  margin-top: var(--s-7);
  /* section rhythm: the next block (#gridHead) carries no rule of its own, so the hero needs the
     same separation the facet bar and the featured hero use. Only rendered when opted in. */
  margin-bottom: var(--s-7);
  padding-top: var(--s-6);
  border-top: var(--hair) solid var(--line);
  min-width: 0;
}
.store-hero > * { min-width: 0; }

/* ── operator copy wraps; it never widens the page ───────────────────────────
 * Every field is capped server-side, but a cap is not a width: a 160-char
 * headline or a 90-char mark can still be one unbroken token. `anywhere` rather
 * than `break-word` also shrinks the element's min-content contribution, so a
 * grid/flex track cannot be blown out by it (verified at 320px, all fields at
 * their caps). It has no effect on shipped copy, which never needs to break. */
#storefront .eyebrow,
.wordmark,
#storeHero .hero-title,
#storeHero .lede,
.grid-head .view-title,
.grid-head .lede,
.proof-title,
.proof-sub,
.proof-mark,
.proof .fine,
.foot-line,
.foot-facts dt,
.foot-facts dd { overflow-wrap: anywhere; }

.size-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}

/* ── facet bar ────────────────────────────────────────────────────────────── */
.facets {
  display: grid;
  gap: var(--s-4);
  margin: var(--s-7) 0 var(--s-5);
  padding: var(--s-4) 0;
  border-top: var(--hair) solid var(--line);
  border-bottom: var(--hair) solid var(--line);
}
.facets .chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  min-width: 0;
}
.field { position: relative; display: grid; gap: var(--s-1); min-width: 0; }
.field-label,
.field > legend {
  padding: 0;
  font-family: var(--font-mono);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-3);
}
/* The search, sort and shipping controls are emitted by store.js/cart.js with an
   id (or no class at all) and no styling hook, so the field skin keys off `.field`
   plus the ids, with the published class names kept as aliases. Without this the
   controls fall back to UA defaults — measured 22px (select) and 32px (input),
   both below the 44px target and visually off-system. */
.field input:not([type="radio"]):not([type="checkbox"]),
.field select,
#search,
#sort,
#detailSize,
.search-input,
.sort-select,
.size-select {
  width: 100%;
  min-height: 48px;
  padding: 0 var(--s-4);
  background: var(--panel);
  color: var(--text);
  border: var(--hair) solid var(--line-2);
  border-radius: var(--r-plate);
  font-family: var(--font-mono);
  font-size: var(--t-data);
  font-variant-numeric: tabular-nums;
}
.field input[type="search"],
#search,
.search-input { font-family: var(--font-sans); font-size: var(--t-sm); }
.field input::placeholder,
#search::placeholder,
.search-input::placeholder { color: var(--text-3); }
.field input:hover,
.field select:hover,
#search:hover,
#sort:hover,
.search-input:hover,
.sort-select:hover,
.size-select:hover { border-color: var(--cyan-dim); }
.field select,
#sort,
.sort-select,
.size-select { cursor: pointer; }
.result-count {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  color: var(--text-2);
  min-height: 1.2em;
}

/* the <noscript> block is a full designed view, not a bare paragraph */
.noscript { margin-top: 0; }
.noscript .view-title { color: var(--text); }

/* ── the specimen-plate grid ──────────────────────────────────────────────── */
.tiles {
  display: grid;
  gap: var(--s-6) var(--s-4);
  grid-template-columns: 1fr;
  align-items: start;
}
.tile {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  min-width: 0;
}
.tile-link {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: var(--s-3);
  min-width: 0;
  color: inherit;
}
.tile-body { display: grid; gap: var(--s-2); align-content: start; }
.tile-title {
  font-family: var(--font-sans);
  font-size: 1.0625rem;
  font-weight: 700;
  line-height: 1.22;
  letter-spacing: var(--tr-h3);
  text-transform: uppercase;
  color: var(--text);
}
.tile-link:hover .tile-title { color: var(--cyan); }
.tile-tagline {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: var(--t-sm);
  line-height: 1.45;
  color: var(--text-2);
}
.tile-price {
  display: flex;
  align-items: baseline;
  gap: var(--s-2);
  margin-top: var(--s-1);
  font-family: var(--font-mono);
  font-size: 1.0625rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.tile-price .from {
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
}
.tile-body .spec-row { padding-block: var(--s-1); }
.tile-sizes { display: flex; flex-wrap: wrap; gap: var(--s-2); }

/* #gridEmpty appears both inside the grid view (filters matched nothing) and as
   the body of the whole empty view. */
#gridEmpty { margin-top: var(--s-7); }
.empty {
  display: grid;
  gap: var(--s-4);
  align-content: center;
  padding: var(--s-6);
  border: var(--hair) solid var(--line);
  border-radius: var(--r-plate);
  background:
    repeating-linear-gradient(135deg, transparent 0 7px, rgba(31, 42, 51, 0.32) 7px 8px),
    var(--ink);
}
#storefront[data-view="empty"] .empty { margin-top: var(--s-6); }
.empty-title {
  font-family: var(--font-sans);
  font-weight: var(--fw-display);
  font-size: var(--t-h2);
  line-height: var(--lh-h2);
  letter-spacing: var(--tr-h2);
  text-transform: uppercase;
  color: var(--text);
}
.empty-actions { display: flex; flex-wrap: wrap; gap: var(--s-3); }

/* ═══ 6 · detail view ═══════════════════════════════════════════════════════ */

.detail { display: grid; gap: var(--s-6); }
.crumbs {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-3);
}
.crumbs a { display: inline-flex; align-items: center; min-height: 44px; }

.detail-grid { display: grid; gap: var(--s-6); align-items: start; }
.gallery { display: grid; gap: var(--s-4); min-width: 0; align-content: start; }
.gallery-thumbs { display: flex; flex-wrap: wrap; gap: var(--s-3); }
.thumb {
  display: grid;
  place-items: center;
  width: 74px;
  height: 74px;
  /* ⚠ V3-1: NOT a percentage. `padding: 6%` was copied from .plate-art, where 6% of a ~614px
     plate is a sensible inset. Percentage padding resolves against the CONTAINING BLOCK's inline
     size, not the element's own — so on a fixed 74px thumb inside a 614px .gallery-thumbs it became
     36.83px per side. Under border-box that left a 74 − 2×36.83 − 2×hair = 2×2 content box, and the
     thumbnail image rendered at 0×0: an empty bone square on every listing that has a sibling
     print. Measured on the live payload (4 of the 7 products have a sibling). A fixed 5px inset is
     6.8% of the thumb, i.e. the visual proportion the rule was originally after. */
  padding: 5px;
  background: var(--plate);
  border: var(--hair) solid var(--plate-line);
  border-radius: var(--r-plate);
  overflow: clip;
}
.thumb img { width: 100%; height: 100%; object-fit: contain; }
.thumb:hover { border-color: var(--cyan); }

.detail-copy {
  display: grid;
  gap: var(--s-4);
  align-content: start;
  min-width: 0;
}
.detail-copy .view-title { color: var(--text); }
.price-lg {
  font-family: var(--font-mono);
  font-size: clamp(1.5rem, 3.4vw, 2.125rem);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.buy {
  display: grid;
  gap: var(--s-4);
  padding: var(--s-5);
  border: var(--hair) solid var(--line);
  background: var(--panel);
  border-radius: var(--r-plate);
}
.sizes {
  display: grid;
  gap: var(--s-2);
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}
.sizes > legend { margin-bottom: var(--s-1); }
.sizes .size-select { max-width: 24rem; }
.buy-price {
  font-family: var(--font-mono);
  font-size: 1.25rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.buy-hint { margin-top: calc(-1 * var(--s-2)); }
.transit,
.ddp {
  display: grid;
  max-width: var(--maxw-prose);
  padding-left: var(--s-4);
  border-left: var(--focus-w) solid var(--line-2);
  font-family: var(--font-mono);
  font-size: var(--t-data);
  line-height: var(--lh-data);
  color: var(--text-2);
}
.transit { border-left-color: var(--cyan-dim); }
.ddp { border-left-color: var(--ok); }
.warn-blank {
  display: grid;
  gap: var(--s-1);
  max-width: var(--maxw-prose);
  padding: var(--s-4);
  border: var(--hair) solid var(--bad);
  border-left-width: 3px;
  border-radius: var(--r-plate);
  background: rgba(255, 90, 78, 0.08);
  font-family: var(--font-mono);
  font-size: var(--t-data);
  line-height: var(--lh-data);
  color: var(--text);
}
.warn-blank strong { color: var(--bad); text-transform: uppercase; letter-spacing: 0.04em; }

.specs { display: grid; min-width: 0; }
.detail-copy .specs,
.buy .specs,
.detail-desc .specs { border-top: var(--hair) solid var(--line); }
.detail-copy .specs .spec-row,
.detail-desc .specs .spec-row { border-bottom: var(--hair) solid var(--line); }

.detail-desc {
  display: grid;
  gap: var(--s-4);
  padding-top: var(--s-6);
  border-top: var(--hair) solid var(--line);
}
.detail-desc > h2 {
  font-family: var(--font-sans);
  font-size: var(--t-h3);
  font-weight: 700;
  letter-spacing: var(--tr-h3);
  text-transform: uppercase;
  color: var(--text);
}
.prose { display: grid; gap: var(--s-4); max-width: var(--maxw-prose); color: var(--text-2); }
.prose strong { color: var(--text); }
.bullets { display: grid; gap: var(--s-2); font-size: var(--t-sm); color: var(--text-2); }
.bullets li { display: grid; grid-template-columns: 1.2em 1fr; gap: var(--s-2); }
.bullets li::before { content: "—"; font-family: var(--font-mono); color: var(--cyan-dim); }
.care-note { margin-top: var(--s-2); }

/* ═══ 7 · cart view (deep-linkable /store/cart) + shared line items ═════════
 * cart.js emits .cart-page > .cart-page-head + .cart-lines + .cart-sum for the
 * page and the same .line markup inside #drawerBody. .cart-line* is kept as an
 * alias only so no other writer's variant renders unstyled. */

.cart-page { display: grid; gap: var(--s-6); align-items: start; }
.cart-page-head { display: grid; gap: var(--s-3); }
.cart-lines { display: grid; gap: var(--s-4); min-width: 0; }
.line,
.cart-line {
  display: grid;
  grid-template-columns: 72px minmax(0, 1fr);
  gap: var(--s-3) var(--s-4);
  padding: var(--s-4);
  border: var(--hair) solid var(--line);
  background: var(--panel);
  border-radius: var(--r-plate);
}
.line-art,
.cart-line-art {
  display: grid;
  place-items: center;
  aspect-ratio: 1 / 1;
  align-self: start;
  padding: 5%;
  background: var(--plate);
  border: var(--hair) solid var(--plate-line);
  border-radius: var(--r-plate);
}
.line-art img,
.cart-line-art img { width: 100%; height: 100%; object-fit: contain; }
.line-body,
.cart-line-main { display: grid; gap: var(--s-2); align-content: start; min-width: 0; }
.line-title,
.cart-line-title,
.line-body h2,
.cart-line-main h2,
.cart-line-main h3 {
  font-family: var(--font-sans);
  font-size: var(--t-h3);
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: var(--tr-h3);
  text-transform: uppercase;
  color: var(--text);
}
.line-title a:hover,
.cart-line-title a:hover,
.line-body h2 a:hover { color: var(--cyan); }
/* mobile: the price + Remove sit under the facts, inside the text column */
.line-end,
.cart-line-end {
  grid-column: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
}
.line-price,
.cart-line-price {
  font-family: var(--font-mono);
  font-size: var(--t-data);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.qty,
.qty-row {
  display: inline-flex;
  align-items: center;
  justify-self: start;
  border: var(--hair) solid var(--line-2);
  border-radius: var(--r-pill);
  overflow: hidden;
}
.qty-btn,
.qty-row button {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 1rem;
  line-height: 1;
}
.qty-btn:hover,
.qty-row button:hover { background: var(--panel-2); color: var(--cyan); }
.qty-n,
.qty-row output {
  min-width: 40px;
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--t-data);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.link-btn {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0;
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-decoration: underline;
  text-underline-offset: 3px;
  color: var(--text-3);
}
.link-btn:hover { color: var(--bad); }

.cart-sum,
.drawer-sum { display: grid; gap: var(--s-4); align-content: start; }
.cart-sum {
  padding: var(--s-5);
  border: var(--hair) solid var(--line);
  background: var(--panel);
  border-radius: var(--r-plate);
}
/* the summary is a spec sheet: ruled rows, total last and heaviest */
.cart-sum .specs,
.drawer-sum .specs { border-top: var(--hair) solid var(--line); }
.cart-sum .specs .spec-row,
.drawer-sum .specs .spec-row { border-bottom: var(--hair) solid var(--line); }
.cart-sum .specs .spec-row:last-child,
.drawer-sum .specs .spec-row:last-child { border-bottom: 0; }
.cart-sum .specs strong,
.drawer-sum .specs strong { color: var(--text); font-weight: 700; }
.ship-note {
  min-height: 1.2em;
  font-family: var(--font-mono);
  font-size: var(--t-label);
  line-height: 1.7;
  color: var(--text-3);
}
.ship-note:empty { display: none; }
.ship-form { display: grid; gap: var(--s-3); }
.ship-out { display: grid; gap: var(--s-3); }
.ship-body { display: grid; gap: var(--s-2); }
/* cart.js sets data-tone, not a colour class: the quote status must be able to
   say "asking the facility" / "rates found" / "rate limited" honestly. */
.ship-note[data-tone="none"] { color: var(--text-3); }
.ship-note[data-tone="wait"] { color: var(--text-2); }
.ship-note[data-tone="ok"] { color: var(--ok); }
.ship-note[data-tone="err"] { color: var(--bad); }
.ship-group { display: grid; gap: var(--s-2); padding-top: var(--s-3); border-top: var(--hair) solid var(--line); }
.ship-group-title {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-2);
}
.ship-group-title .fine { display: block; margin-top: 4px; font-weight: 400; letter-spacing: 0.02em; }
.ship-opt {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  min-height: 44px;
  padding: 0 var(--s-3);
  border: var(--hair) solid transparent;
  border-radius: var(--r-plate);
  font-family: var(--font-mono);
  font-size: var(--t-data);
  font-variant-numeric: tabular-nums;
  color: var(--text-2);
  cursor: pointer;
}
.ship-opt input[type="radio"] { flex: 0 0 auto; width: 20px; height: 20px; accent-color: var(--cyan); }
.ship-opt:hover { color: var(--text); border-color: var(--line-2); }
.ship-opt.is-on { color: var(--text); border-color: var(--cyan); background: var(--cyan-ink); }
.ship-label { flex: 1 1 auto; min-width: 0; }
.ship-price { font-weight: 700; color: var(--text); }
.ship-eta { flex: 0 0 auto; }
.status {
  display: grid;
  gap: var(--s-1);
  padding: var(--s-3) var(--s-4);
  border: var(--hair) solid var(--line-2);
  border-radius: var(--r-plate);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  line-height: 1.6;
  color: var(--text-2);
}
.status.err { border-color: var(--bad); color: var(--text); }
.status.ok { border-color: var(--ok); color: var(--text); }
.status:empty { display: none; }
.cart-empty { text-align: left; }
.cart-empty .empty-title { font-size: var(--t-h2); }

/* ═══ 8 · overlays: quick-add, toast ═══════════════════════════════════════ */

#quickAdd,
.quickadd {
  width: min(460px, calc(100vw - 2 * var(--s-4)));
  max-width: 100%;
  max-height: calc(100dvh - 2 * var(--s-5));
  overflow: auto;
  padding: var(--s-5);
  background: var(--ink);
  color: var(--text);
  border: var(--hair) solid var(--line-2);
  border-radius: var(--r-plate);
}
#quickAdd::backdrop,
.quickadd::backdrop { background: var(--scrim); }
.quickadd .field { margin-top: var(--s-4); }
.quickadd-body { display: grid; gap: var(--s-3); margin-top: var(--s-3); }
.quickadd-body .sizes { margin-top: var(--s-1); }
.quickadd-title {
  font-family: var(--font-sans);
  font-size: var(--t-h3);
  font-weight: 700;
  letter-spacing: var(--tr-h3);
  text-transform: uppercase;
  color: var(--text);
}

/* #toast: geometry and skin only. motion.js owns opacity/transform and the
   [data-mm-show]/is-on state; the only display rule is for the empty region,
   which has nothing to announce and must never intercept a tap.
   The pill skin lives on the CONTAINER, not on its children: motion.js appends
   .toast-msg + .toast-close, so skinning `#toast > *` boxed each child separately
   and rendered three adjacent panels (measured defect). */
#toast,
.toast {
  position: fixed;
  left: var(--s-4);
  right: auto;
  bottom: var(--s-4);
  z-index: var(--z-toast);
  display: flex;
  align-items: center;
  gap: var(--s-3);
  width: max-content;
  max-width: min(calc(100vw - 2 * var(--s-4)), 420px);
  padding: var(--s-3) var(--s-4);
  background: var(--panel);
  color: var(--text);
  border: var(--hair) solid var(--cyan-dim);
  border-radius: var(--r-plate);
  font-family: var(--font-mono);
  font-size: var(--t-data);
  line-height: 1.4;
  font-variant-numeric: tabular-nums;
}
#toast:empty { display: none; }
#toast > *,
.toast > * {
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
}
#toast .toast-msg,
.toast .toast-msg { flex: 1 1 auto; }
#toast .toast-close,
.toast .toast-close {
  flex: 0 0 auto;
  display: inline-grid;
  place-items: center;
  width: 44px;
  height: 44px;
  margin-right: calc(-1 * var(--s-2));
  color: var(--text-2);
  font-family: var(--font-mono);
  font-size: var(--t-body);
  line-height: 1;
}
#toast .toast-close:hover, .toast .toast-close:hover { color: var(--cyan); }
/* MEASURED COLLISION (390×844, real page): with the drawer open the toast's
   bottom anchor (y766–828) landed squarely on #drawerSummary (y326–844) and
   covered the drawer's checkout/foot line. On ≤640px the drawer is full-height,
   so the bottom edge belongs to the drawer: dock the confirmation to the top
   instead, with enough width reserved on the right that it can never sit over the
   44px #drawerClose target. --z-toast (300) already beats --z-drawer (200), so the
   confirmation deliberately stays on top. The hook is motion-polish's
   html[data-mm-drawer="open"], set from cart.js's data-open. */
@media (max-width: 640px) {
  html[data-mm-drawer="open"] #toast,
  html[data-mm-drawer="open"] .toast {
    top: var(--s-3);
    bottom: auto;
    left: var(--s-3);
    max-width: calc(100vw - var(--s-3) - 96px);
  }
}

/* ── buttons ──────────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: 48px;
  padding: 0 var(--s-5);
  border: var(--hair) solid var(--line-2);
  border-radius: var(--r-plate);
  background: var(--panel);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: var(--t-data);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-align: center;
  transition: color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.btn:hover { color: var(--cyan); border-color: var(--cyan-dim); }
.btn-primary {
  background: var(--cyan);
  border-color: var(--cyan);
  color: var(--void);
  box-shadow: var(--bloom);
}
.btn-primary:hover { color: var(--void); filter: brightness(1.06); }
.btn-block { width: 100%; }
.btn.is-on { filter: brightness(0.94); }
/* Disabled must stay legible: --text-3 on --panel is 5.35:1, so the honest
   "Checkout — not live yet" button reads rather than fades out. */
.btn[disabled],
.btn:disabled {
  background: var(--panel);
  border-color: var(--line);
  color: var(--text-3);
  box-shadow: none;
  cursor: not-allowed;
}
.btn[disabled]:hover,
.btn:disabled:hover { color: var(--text-3); border-color: var(--line); }

/* ═══ 9 · responsive ═══════════════════════════════════════════════════════ */

@media (min-width: 600px) {
  .view { padding-top: var(--s-7); }
  .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .foot-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .stat { flex: 1 1 30%; }
  .cart-page .line { grid-template-columns: 104px minmax(0, 1fr) auto; }
  .cart-page .line-end { grid-column: auto; display: grid; gap: var(--s-3); justify-items: end; align-content: end; }
  .facets { grid-template-columns: minmax(0, 1fr) 220px; align-items: end; }
  .facets .chips { grid-column: 1 / -1; }
  .facets .result-count { grid-column: 1 / -1; }
}

@media (min-width: 768px) {
  .nav-in { min-height: var(--nav-h); }
  .stat { flex: 1 1 20%; }
}

@media (min-width: 900px) {
  .tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-7) var(--s-5); }
  .hero { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); gap: var(--s-8); align-items: center; }
  .detail-grid { grid-template-columns: minmax(0, 1.02fr) minmax(0, 1fr); gap: var(--s-8); }
  .gallery { position: sticky; top: calc(var(--nav-h) + var(--s-5)); }
  .facets { grid-template-columns: minmax(0, 1fr) 210px 170px; }
  .facets .chips { grid-column: 1; }
}

@media (min-width: 1000px) {
  .cart-page { grid-template-columns: minmax(0, 1fr) minmax(320px, 400px); gap: var(--s-8); }
  .cart-page-head { grid-column: 1 / -1; }
  .cart-page .cart-sum { position: sticky; top: calc(var(--nav-h) + var(--s-5)); }
  .foot-facts { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}

@media (min-width: 1280px) {
  .tiles { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* Narrow phones: keep the header on one line and stop the stats strip from
   squeezing into unreadable columns. */
@media (max-width: 380px) {
  .nav-in { gap: var(--s-2); padding-inline: var(--s-3); }
  .wordmark { font-size: 0.8125rem; }
  .nav-link { padding-inline: var(--s-1); }
  .view, .foot { padding-inline: var(--s-3); }
  .stat { min-width: 100%; padding-right: 0; }
  .stat + .stat { padding-left: 0; border-left: 0; border-top: var(--hair) solid var(--line); }
}

@media (max-width: 560px) {
  #quickAdd, .quickadd { width: 100vw; max-height: 100dvh; border-radius: 0; }
}

/* ═══ 10 · reduced motion — belt and braces with tokens.css ════════════════ */

@media (prefers-reduced-motion: reduce) {
  .skip { transition: none; }
  #view > * { animation: none; }
  .boot .plate.is-sk { animation: none; }
  .tile-link:hover .plate-art img,
  .tile-link:focus-visible .plate-art img { transform: none; }
  #ticker { display: none !important; }
}

/* Forced colours: hairlines carry the structure, so keep them literal. */
@media (forced-colors: active) {
  .plate, .thumb, .cart-line-art { border: 1px solid CanvasText; }
  .chip[aria-pressed="true"] { border: 2px solid Highlight; }
}

/* ── footer link rows (Phase 3: legal + family) ─────────────────────────────
 * The storefront's `.foot a` rule paints links cyan/underlined. These rows are
 * metadata, not calls to action, so they take the quiet mono treatment. Appended
 * after the base rule so the intended colour wins at equal specificity.
 */
.foot .foot-links {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--t-label);
  line-height: 1.8;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-3);
}
.foot .foot-links a { color: var(--text-2); text-decoration: none; }
.foot .foot-links a:hover { color: var(--text); }
.foot .foot-family { color: var(--text-3); }
