/* ─────────────────────────────────────────────────────────────────────────────
 * Metatron Mind — storefront design tokens + primitives.  "Bone Terminal"
 *
 * OWNER: design-system.  Consumers: storefront.css (shell-ui), store.js/cart.js
 * (via the class vocabulary below), designsystem.html (the visual QA surface).
 * AUTHORITY: docs/STOREFRONT-DESIGN.md §1–§4 (as amended 2026-10-15). Change a
 * value here and the doc must change in the same edit — they are one contract.
 *
 * CONTRAST METHOD — every ratio in this file is MEASURED, never estimated.
 *   WCAG 2.1 relative luminance, sRGB, 8-bit channel values, no rounding before
 *   the final division; ratio = (Llight + .05) / (Ldark + .05).
 *   Thresholds: 4.5:1 body/label text · 3:1 UI borders, icons, control edges ·
 *   decorative hairlines have NO minimum (they carry no information).
 *   designsystem.html recomputes every one of these AT RUNTIME in-page and prints
 *   PASS/FAIL, so the numbers below are checkable rather than asserted:
 *   open assets/designsystem.html?check=1
 *
 * RULES
 *   · Dark-first. There is no light mode: the product mockups are on pure white
 *     and a light mode would make them invisible.
 *   · No colour exists outside §1. Nothing here is "close enough" by taste.
 *   · The hairline is the design. No load-bearing shadows, no glassmorphism, no
 *     soft glows except the single sanctioned --bloom.
 *   · --cyan-dim and --plate-edge are BORDER values. Never text. Where a dim
 *     border would fail 3:1 on the local ground, the rule is stated in place.
 *   · --glitch is rationed to ONE element per page, never text, never a button.
 * ──────────────────────────────────────────────────────────────────────────── */

/* ── 0. fonts — self-hosted, same origin, system-stack fallback ──────────────
 * §2 revision 2 (2026-10-15). The earlier "fonts are blocked by the MIME map"
 * finding was RETRACTED: it came from a truncated 6,400 B body and a 404 that
 * returned application/json, not from the response headers. Chromium sniffs the
 * sfnt signature and ignores the generic MIME type, so server.mjs stays frozen.
 * Measured on this server, served by server.mjs as application/octet-stream +
 * nosniff: document.fonts face status "loaded", glyph advance 133.3px styled vs
 * 115.5px serif fallback, zero console messages.
 *
 * TWO files, 89,028 B total, zero third-party requests. Licences, provenance and
 * the exact reproduction commands: app/public/assets/fonts/README.md.
 *   archivo-var-latin.woff2         57,688 B  Archivo        wght 400–800 · wdth 75–100 · latin
 *   jetbrains-mono-var-latin.woff2  31,340 B  JetBrains Mono wght 400–700 · latin
 * Both are SIL OFL 1.1. The Archivo file is an OFL-permitted axis-restricted
 * build of Google's latin wdth+wght variable (90,096 B): restricting it to the
 * two axes §2 actually names holds the §6 font budget WITHOUT giving up wdth.
 *
 * THE wdth AXIS IS THE DISPLAY VOICE — do not flatten it. Measured advance for
 * "METATRON" at 100px: 548.1px at wght 400 / wdth 100% · 589.0px at 800 / 100% ·
 * 462.4px at 800 / var(--wd-display). That compression is what weight alone
 * cannot reach, and it is why --font-display is the same family as --font-sans
 * with a different stretch.
 *
 * font-display: swap · unicode-range latched to latin · every token keeps its
 * system stack declared behind the real family, so a failed load degrades to the
 * platform face rather than to nothing.
 *
 * PRELOAD (before the tokens.css <link>; `crossorigin` is mandatory for fonts or
 * the browser fetches them twice). For shell-ui — verified no double-fetch with
 * type="font/woff2" even though server.mjs sends application/octet-stream:
 *   <link rel="preload" href="__BASE__/assets/fonts/archivo-var-latin.woff2" as="font" type="font/woff2" crossorigin>
 *   <link rel="preload" href="__BASE__/assets/fonts/jetbrains-mono-var-latin.woff2" as="font" type="font/woff2" crossorigin>
 * --------------------------------------------------------------------------- */
/* Archivo, latin. Range-declared so CSS drives the axes: font-stretch 78% picks
 * the compressed display master, 400–800 picks the weight. */
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 400 800;
  font-stretch: 75% 100%;
  font-display: swap;
  src: url("fonts/archivo-var-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
                 U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
                 U+2212, U+2215, U+FEFF, U+FFFD;
}
/* JetBrains Mono, latin. Every label, spec key, price, size and code. */
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("fonts/jetbrains-mono-var-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
                 U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
                 U+2212, U+2215, U+FEFF, U+FFFD;
}
:root {
  --font-sans:    "Archivo", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-display: "Archivo", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
  --font-mono:    "JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  --fw-regular:  400;
  --fw-semibold: 600;
  --fw-display:  800;        /* §2: display is 800, not 900 — heavy, not novelty */
  --wd-display:  78%;        /* §2 wants wdth 75–80 for the display voice */
  --tr-display: -0.04em;
}

/* ═══ 1. COLOUR — §1, locked ════════════════════════════════════════════════ */
:root {
  /* ── ground & surfaces ──────────────────────────────────────────────────── */
  --void:        #07090B;   /* page ground */
  --ink:         #0C1013;   /* raised: header, drawer, footer */
  --panel:       #12181D;   /* cards on dark, inputs */
  --panel-2:     #1A2229;   /* hover / selected on dark */
  --line:        #1F2A33;   /* hairline on dark — DECORATIVE: 1.36:1 on --void, no minimum */
  --line-2:      #2C3A45;   /* stronger divider — DECORATIVE: 1.71:1 on --void */
  --plate:       #F2EFE8;   /* BONE — the specimen plate behind product art */
  --plate-2:     #E6E1D6;   /* plate caption rail */
  --plate-line:  #B8B0A0;   /* hairline ON a plate — DECORATIVE: 1.87:1 on --plate */
  --plate-edge:  #8A8272;   /* interactive control edge ON a plate: 3.31:1 on --plate ✓AA-UI */

  /* ── type on dark ───────────────────────────────────────────────────────── */
  --text:        #F2EFE8;   /* 17.37:1 --void · 16.64:1 --ink · 15.58:1 --panel */
  --text-2:      #9FAEB8;   /*  8.76:1 --void ·  7.85:1 --panel ·  7.07:1 --panel-2 */
  --text-3:      #7E8F9A;   /*  5.97:1 --void ·  5.35:1 --panel — labels/tertiary, never body */
  --ink-text:    #12181D;   /* type ON bone: 15.58:1 --plate · 13.72:1 --plate-2 */
  --ink-text-2:  #5A6570;   /* secondary ON bone: 5.18:1 --plate · 4.56:1 --plate-2 */

  /* ── signal ─────────────────────────────────────────────────────────────── */
  --cyan:        #00E5D0;   /* the accent: 12.46:1 --void · 11.17:1 --panel · 1.39:1 --plate ✗ */
  --cyan-dim:    #0A6E68;   /* dimmed cyan BORDER: 3.27:1 --void ✓ · 3.13:1 --ink ✓ · 2.93:1 --panel ✗ */
  --cyan-ink:    #062E2C;   /* cyan-tinted dark fill (ground for cyan text) */
  --cyan-on-plate: #0A6E68; /* the ONLY cyan permitted as type on bone: 5.31:1 --plate · 4.68:1 --plate-2 */
  --gold:        #FFC24B;   /* earned emphasis, MAX ONE per viewport: 12.41:1 --void · 10.02:1 --panel-2 */
  --bad:         #FF5A4E;   /* errors, destruction: 6.48:1 --void · 5.81:1 --panel */
  --ok:          #3DDC84;   /* success confirmation: 11.18:1 --void · 10.02:1 --panel */

  /* ── signature — RATIONED: one element per page, never text/buttons/wash ── */
  --glitch: linear-gradient(92deg, #00E5D0 0%, #FFC24B 52%, #FF4FD8 100%);

  /* ── interaction fills (--void is the only permitted text colour on cyan) ─ */
  --on-signal:     #07090B;                  /* text on --cyan: 12.46:1 ✓ */
  --on-signal-dim: rgba(7, 9, 11, .68);      /* --void at 68% over --cyan = #054742: 5.90:1 ✓ */
  --scrim:         rgba(7, 9, 11, .72);      /* --void at 72% — modal ground */
}

/* ═══ 2. TYPE — §2, as amended: system stacks ═══════════════════════════════ */
:root {
  /* fluid scale */
  --t-hero:  clamp(2.75rem, 9vw, 7.5rem);
  --t-h1:    clamp(2rem, 5vw, 3.25rem);
  --t-h2:    clamp(1.375rem, 2.4vw, 1.875rem);
  --t-h3:    1.125rem;
  --t-body:  1rem;
  --t-sm:    0.875rem;
  --t-label: 0.6875rem;   /* 11px — mono only; --text-3 clears 4.5:1 at this size (5.35:1) */
  --t-data:  0.8125rem;

  /* tracking */
  --tr-hero:  -0.04em;
  --tr-h1:    -0.03em;
  --tr-h2:    -0.02em;
  --tr-h3:    -0.01em;
  --tr-body:  0;
  --tr-label: 0.14em;     /* UPPERCASE label tracking */

  /* leading */
  --lh-hero:  0.88;
  --lh-h1:    0.95;
  --lh-h2:    1.1;
  --lh-h3:    1.25;
  --lh-body:  1.6;
  --lh-sm:    1.5;
  --lh-label: 1.2;
  --lh-data:  1.4;

  /* measure */
  --maxw-prose: 62ch;
}

/* ═══ 3. SPACE · SHAPE · MOTION — §3 ════════════════════════════════════════ */
:root {
  --s-1: 4px;  --s-2: 8px;   --s-3: 12px; --s-4: 16px; --s-5: 24px;
  --s-6: 32px; --s-7: 48px;  --s-8: 64px; --s-9: 96px; --s-10: 144px;

  --hair: 1px;
  --r-plate: 2px;    /* a print does not have rounded corners */
  --r-pill: 999px;   /* chips and badges only */
  --maxw: 1360px;

  /* grid: 12 desktop / 6 tablet / 4 mobile, gutters --s-5 */
  --grid-cols: 12;
  --grid-cols-md: 6;
  --grid-cols-sm: 4;
  --gutter: var(--s-5);

  --drawer-w: 420px;      /* mobile: full width, see §4.4 */
  --nav-h: 64px;

  --ease:          cubic-bezier(.22,.61,.36,1);
  --ease-out-expo: cubic-bezier(.16,1,.3,1);
  --dur-fast: 120ms;
  --dur:      240ms;
  --dur-slow: 560ms;

  --bloom: 0 0 40px rgba(0, 229, 208, .12);   /* the ONE sanctioned glow, ≤12% */
  --z-nav: 100;
  --z-drawer: 200;
  --z-toast: 300;

  /* focus: 2px --cyan with a 2px offset gap */
  --focus-w: 2px;
  --focus-off: 2px;
}

/* ═══ 4. PRIMITIVES — §4 ════════════════════════════════════════════════════
 * Four primitives. Their class vocabulary is FROZEN — published to
 * commerce-behavior and consumed by storefront.css:
 *   .plate > .plate-art (img) + .plate-rail
 *   .spec-row > .spec-key + .lead + .spec-val
 *   .chip > .chip-n
 *   .drawer, .drawer-scrim, .drawer-head, .drawer-body, .drawer-foot, .drawer-close
 * Rename nothing without the Lead. shell-ui owns LAYOUT overrides in
 * storefront.css; the visual treatment lives here so there is one source of truth.
 * ──────────────────────────────────────────────────────────────────────────── */

/* ── shared type atoms — §2 house rules ───────────────────────────────────── */
.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.tnum { font-variant-numeric: tabular-nums; }
.label {
  font-family: var(--font-mono); font-size: var(--t-label); line-height: var(--lh-label);
  letter-spacing: var(--tr-label); text-transform: uppercase; font-variant-numeric: tabular-nums;
}
.data {
  font-family: var(--font-mono); font-size: var(--t-data); line-height: var(--lh-data);
  letter-spacing: 0.01em; font-variant-numeric: tabular-nums;
}
/* the [BRACKET] motif — `.eyebrow` is TYPE ONLY and MUST NOT inject the brackets.
 * Ownership rule, after a real double-bracket defect on the storefront (2026-10-15):
 * store.js authors the motif literally — "[ ARCHIVE ]", "[ FEATURED ]", "[ 404 ]" —
 * which is the brand's own convention (the product art reads "[TRUTH]"). A CSS
 * ::before/::after pair here therefore rendered "[[ ARCHIVE ]]" on 11 eyebrows.
 * THE CONTENT OWNS THE BRACKETS; the stylesheet owns the type. Write them:
 *   <span class="eyebrow">[ Archive ]</span>   → [ ARCHIVE ]
 * If you need them read aloud to differ from the visible text, wrap the brackets in
 * a span with aria-hidden="true" at the call site. Do not re-add content here. */
.eyebrow {
  display: inline-block;
  font-family: var(--font-mono); font-size: var(--t-label); line-height: var(--lh-label);
  letter-spacing: var(--tr-label); text-transform: uppercase;
  color: var(--cyan);            /* 12.46:1 on --void ✓ */
}
.eyebrow--on-plate { color: var(--cyan-on-plate); }   /* 5.31:1 on --plate ✓ — the only cyan-on-bone */

/* type steps, straight from the §2 table. Display = UPPERCASE + negative tracking. */
.t-hero {
  font-family: var(--font-display); font-weight: var(--fw-display); font-stretch: var(--wd-display);
  font-size: var(--t-hero); line-height: var(--lh-hero); letter-spacing: var(--tr-hero);
  text-transform: uppercase; text-wrap: balance;
}
.t-h1 {
  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;
}
.t-h2 {
  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;
}
.t-h3 {
  font-family: var(--font-sans); font-weight: var(--fw-semibold);
  font-size: var(--t-h3); line-height: var(--lh-h3); letter-spacing: var(--tr-h3);
}
.t-body { font-family: var(--font-sans); font-size: var(--t-body); line-height: var(--lh-body); letter-spacing: 0; }
.t-sm   { font-family: var(--font-sans); font-size: var(--t-sm);   line-height: var(--lh-sm); }
.t-label {
  font-family: var(--font-mono); font-size: var(--t-label); line-height: var(--lh-label);
  letter-spacing: var(--tr-label); text-transform: uppercase; font-variant-numeric: tabular-nums;
}
.t-data {
  font-family: var(--font-mono); font-size: var(--t-data); line-height: var(--lh-data);
  letter-spacing: 0.01em; font-variant-numeric: tabular-nums;
}

/* the single rationed gradient element: ONE per page, never on text */
.glitch-rule {
  height: 2px; width: 100%; border: 0; margin: 0;
  background-image: var(--glitch);
}

/* ── 4.1 SPECIMEN PLATE ────────────────────────────────────────────────────
 * The fix for "every mockup is a white square dumped on a dark page": the white
 * is CURATED into an archive plate rather than hidden. Bone field, near-square
 * radius, hairline ON the plate, art contained with 6% breathing room, mono
 * caption rail. Hover/focus: cyan outer hairline + 1.02 art scale.
 * The rail's third slot is customer-safe mono text — fit, weight class, or
 * nothing. Product styleCode is internal fulfilment data and MUST NOT appear
 * here (§6 truthfulness). Recommended real values: UNISEX / WOMEN, or omit. */
.plate {
  --plate-ratio: 1 / 1;
  --spec-leader: var(--plate-line);
  position: relative;
  display: flex; flex-direction: column;
  margin: 0;
  background: var(--plate);
  border: var(--hair) solid var(--plate-line);   /* decorative hairline */
  border-radius: var(--r-plate);
  overflow: clip;
  transition: border-color var(--dur) var(--ease);
}
.plate-art {
  display: grid; place-items: center;
  aspect-ratio: var(--plate-ratio);
  padding: 6%;
  background: var(--plate);
  /* HARDENED 2026-10-15 for a Safari-only failure the operator reported and the Web Inspector
     captured: the photo rendered at 1493x1697 inside a 767px frame and bled over the text column,
     while `overflow: clip` was the only containment. Root cause NOT reproduced here — Chromium and
     the computed styles are correct, and forcing max-height/min-width changes nothing, so this is
     a Safari containment/repaint difference rather than a cascade bug. These two declarations are
     belt-and-braces and cost nothing where the layout is already correct:
       · `overflow: hidden` as well as `clip`. Safari has supported `clip` only since 16, and
         `clip` also permits `overflow-clip-margin` spill; `hidden` is the older, stronger clip.
         `clip` is kept FIRST so browsers that support it still get the non-scrolling behaviour.
       · `overflow-clip-margin: 0` so no descendant may paint outside the frame even if a future
         scroll container is introduced. */
  overflow: clip;
  overflow: hidden;
  overflow-clip-margin: 0;
}
.plate-art img, .plate-art svg {
  width: 100%; height: 100%;
  /* max-*/min- guards: a flex/grid child's automatic minimum size can exceed its track when the
     intrinsic image is larger than the frame, which is the exact symptom observed (a 2048x2731
     source inside a 767px box). `max-height` keeps `object-fit: contain` honest if an ancestor ever
     gives the image an indefinite height. */
  min-width: 0; min-height: 0;
  max-width: 100%; max-height: 100%;
  object-fit: contain;              /* never crop the garment */
  transition: transform var(--dur) var(--ease-out-expo);
}
/* states: hover / keyboard focus anywhere inside the plate.
   The cue is the BORDER and only the border. The art used to scale 1.02 here (and 0.995 on
   :active) — removed 2026-10-15 on the operator's call, after measuring it on the live site:
   the image grew ~5.6px every time the cursor crossed a tile, so moving down a grid of 8
   produced a ripple of little zooms. Three things made it the wrong gesture for this storefront:
   the DETAIL page never scales, so the two views disagreed; shrinking the picture inside a fixed
   frame is the stock e-commerce hover, which is exactly the unearned motion this design is
   supposed to avoid; and it was specified in TWO files (the .tile-link rules in storefront.css
   doubled up on these), so a fallback image could still zoom because the is-fallback exception
   there did not cover .plate:hover. One cue, one place, nothing resizes. */
.plate:hover, .plate:focus-within, .plate.is-hover, .plate.is-focus { border-color: var(--cyan); }
/* pressed: acknowledged with a border weight change rather than a size change, so the plate
   never performs a movement under the click. */
.plate:active, .plate.is-active { border-color: var(--cyan); }
.plate-rail {
  display: flex; align-items: center; gap: var(--s-2);
  min-height: 34px;
  padding: var(--s-2) var(--s-4);
  background: var(--plate-2);
  border-top: var(--hair) solid var(--plate-line);
  font-family: var(--font-mono); font-size: var(--t-label); line-height: var(--lh-label);
  letter-spacing: var(--tr-label); text-transform: uppercase; font-variant-numeric: tabular-nums;
  color: var(--ink-text-2);          /* 4.56:1 on --plate-2 ✓ */
}
.plate-face { color: var(--ink-text); font-weight: var(--fw-semibold); }   /* 13.72:1 ✓ */
.plate-gsm  { color: var(--ink-text); }                                    /* 13.72:1 ✓ */
.plate-code { margin-left: auto; color: var(--ink-text-2); }               /* never a styleCode */
.plate-sep  { color: var(--ink-text-2); }

/* ── 4.2 SPEC ROW ──────────────────────────────────────────────────────────
 * Markup is a plain <div> (valid anywhere) with three spans in this order:
 *   <div class="spec-row"><span class="spec-key">Weight</span>
 *     <span class="lead" aria-hidden="true"></span>
 *     <span class="spec-val">300 GSM</span></div>
 * .lead is aria-hidden: it is a printed rule, not content. Deliberately NOT a
 * <dl>: the leader span between dt and dd would break the dl content model. */
.spec-row {
  display: flex; align-items: baseline; gap: var(--s-3);
  padding-block: var(--s-2);
  font-family: var(--font-mono); font-size: var(--t-data); line-height: var(--lh-data);
  letter-spacing: 0.01em; font-variant-numeric: tabular-nums;
  color: var(--text);
}
.spec-key {
  flex: 0 0 auto;
  color: var(--text-2);              /* 8.76:1 on --void ✓ */
  font-size: var(--t-label); line-height: var(--lh-label);
  letter-spacing: var(--tr-label); text-transform: uppercase;
}
.spec-val { flex: 0 0 auto; margin-left: auto; color: var(--text); text-align: right; }
.lead {
  flex: 1 1 var(--s-5); min-width: var(--s-4); height: 0;
  align-self: flex-end; margin-bottom: .40em;
  border-bottom: var(--hair) dotted var(--spec-leader, var(--line-2));  /* decorative */
}
/* on a bone plate: key/value/leader all swap to the plate palette */
.plate .spec-row, .spec-row--on-plate { --spec-leader: var(--plate-line); color: var(--ink-text); }
.plate .spec-key, .spec-row--on-plate .spec-key { color: var(--ink-text-2); }   /* 5.18:1 ✓ */
.plate .spec-val, .spec-row--on-plate .spec-val { color: var(--ink-text); }     /* 15.58:1 ✓ */
.spec-row--on-panel .spec-key { color: var(--text-2); }                        /* 7.85:1 ✓ */
.spec-row--on-panel .spec-val { color: var(--text); }                          /* 15.58:1 ✓ */

/* ── 4.3 DATA CHIP ─────────────────────────────────────────────────────────
 * Pill, mono, count in brackets. Facet filters, sort, and cart controls.
 * Active = --cyan fill + --void text (bone on cyan is 1.39:1 and forbidden).
 * Default (dark ground): text-2 7.85:1 on --panel · on-plate variant: 15.58:1. */
.chip {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2);
  min-height: 44px;       /* §6 target size. UNCONDITIONAL: the (pointer: coarse) media
                             query is not reliably reported by headless/mobile emulation,
                             and §6 grants no mouse exemption. A 44px pill is the look. */
  padding: var(--s-2) var(--s-4);
  background: var(--panel);
  color: var(--text-2);                   /* 7.85:1 on --panel ✓ */
  border: var(--hair) solid var(--line-2);
  border-radius: var(--r-pill);
  font-family: var(--font-mono); font-size: var(--t-label); line-height: var(--lh-label);
  letter-spacing: var(--tr-label); text-transform: uppercase; font-variant-numeric: tabular-nums;
  white-space: nowrap; cursor: pointer;
  -webkit-appearance: none; appearance: none;
  transition: color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.chip-n { color: var(--text-3); }         /* 5.35:1 on --panel ✓ — never body text */
/* `.is-hover` is the forced-state twin of `:hover`, used by designsystem.html to
 * prove the state without a pointer. Same declarations, one selector list. */
.chip:hover:not(:disabled), .chip.is-hover:not(:disabled) { color: var(--text); border-color: var(--cyan); }
.chip:hover:not(:disabled) .chip-n, .chip.is-hover:not(:disabled) .chip-n { color: var(--text-2); }
.chip[aria-pressed="true"], .chip[data-active], .chip.is-active {
  background: var(--cyan); border-color: var(--cyan); color: var(--void);   /* 12.46:1 ✓ */
}
.chip[aria-pressed="true"] .chip-n, .chip[data-active] .chip-n, .chip.is-active .chip-n {
  color: var(--on-signal-dim);            /* --void 68% over --cyan: 5.90:1 ✓ */
}
.chip:disabled, .chip[aria-disabled="true"] {
  color: var(--text-3); border-color: var(--line); background: transparent; cursor: not-allowed;
}
.chip:disabled .chip-n { color: var(--text-3); }
/* On bone, --text-3 is only 2.91:1 — a disabled control is exempt from WCAG, but a
 * readable disabled label costs nothing, so it uses --ink-text-2 (5.18:1) instead. */
.chip--on-plate:disabled, .plate .chip:disabled,
.chip--on-plate[aria-disabled="true"], .plate .chip[aria-disabled="true"] {
  color: var(--ink-text-2); border-color: var(--plate-line);
}
.chip--on-plate:disabled .chip-n, .plate .chip:disabled .chip-n { color: var(--ink-text-2); }
/* chips sitting ON a bone plate (size pickers on the specimen plate) */
.chip--on-plate, .plate .chip {
  background: transparent;
  color: var(--ink-text);                 /* 15.58:1 on --plate ✓ */
  border-color: var(--plate-edge);        /* 3.31:1 control edge on --plate ✓ */
}
.chip--on-plate .chip-n, .plate .chip .chip-n { color: var(--ink-text-2); }  /* 5.18:1 ✓ */
.chip--on-plate:hover:not(:disabled), .plate .chip:hover:not(:disabled),
.chip--on-plate.is-hover:not(:disabled), .plate .chip.is-hover:not(:disabled) {
  color: var(--ink-text); border-color: var(--ink-text);
}
.chip--on-plate[aria-pressed="true"], .plate .chip[aria-pressed="true"] {
  background: var(--cyan); border-color: var(--cyan); color: var(--void);
}
/* in the caption rail the ground is --plate-2, where --plate-edge is only
 * 2.92:1 — use --ink-text-2 (4.56:1) or --ink-text (13.72:1) for the edge. */
.plate-rail .chip { border-color: var(--ink-text-2); }

/* ── 4.4 DRAWER ────────────────────────────────────────────────────────────
 * The cart. Slides from the right, 420px, --ink ground, hairline left edge.
 * Mobile (<640px): full width, no left edge. Focus trap + Escape + focus
 * restore are cart.js's job (behaviour, not paint). Open state is `[data-open]`
 * OR `.is-open` — both supported so neither writer has to guess.
 * Closed state is removed from the a11y tree AND from hit-testing. */
.drawer-scrim {
  position: fixed; inset: 0; z-index: calc(var(--z-drawer) - 1);
  background: var(--scrim);               /* --void at 72% */
  opacity: 0; visibility: hidden;
  transition: opacity var(--dur) var(--ease), visibility 0s linear var(--dur);
}
.drawer-scrim[data-open], .drawer-scrim.is-open { opacity: 1; visibility: visible; transition-delay: 0s; }
.drawer {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: var(--z-drawer);
  display: flex; flex-direction: column;
  width: min(var(--drawer-w), 100%);
  background: var(--ink);
  border-left: var(--hair) solid var(--line-2);   /* decorative: the scrim is the separator */
  transform: translateX(101%);
  visibility: hidden;
  transition: transform var(--dur) var(--ease-out-expo), visibility 0s linear var(--dur);
}
.drawer[data-open], .drawer.is-open { transform: none; visibility: visible; transition-delay: 0s; }
.drawer[hidden] { display: none; }
.drawer-head {
  display: flex; align-items: center; gap: var(--s-4);
  min-height: var(--nav-h); padding: var(--s-4) var(--s-5);
  border-bottom: var(--hair) solid var(--line);
  font-family: var(--font-mono); font-size: var(--t-label); letter-spacing: var(--tr-label);
  text-transform: uppercase; color: var(--text-2);
}
.drawer-body { flex: 1 1 auto; overflow-y: auto; overscroll-behavior: contain; padding: var(--s-5); }
.drawer-foot { border-top: var(--hair) solid var(--line); padding: var(--s-5); background: var(--ink); }
.drawer-close {
  margin-left: auto;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px;      /* a11y target, always */
  background: transparent; color: var(--text-2);
  border: var(--hair) solid var(--line-2); border-radius: var(--r-plate);
  font-family: var(--font-mono); font-size: var(--t-body); line-height: 1;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.drawer-close:hover { color: var(--text); border-color: var(--cyan); }
.drawer-title { color: var(--text); }
.drawer-empty {
  padding: var(--s-8) var(--s-5); text-align: center;
  color: var(--text-2); font-family: var(--font-mono); font-size: var(--t-data); line-height: var(--lh-data);
}
/* keep the mobile switch in ONE place so shell-ui does not invent a second one */
@media (max-width: 640px) {
  .drawer { width: 100%; border-left: 0; }
}

/* ═══ 5. BASE — defaults the shell may override ═════════════════════════════
 * Deliberately at :where() specificity 0 so storefront.css always wins. These
 * exist so the primitives are never rendered against an unstyled ground. */
:where(#storefront) {
  background: var(--void);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
}
:where(#storefront) a { color: var(--cyan); }          /* 12.46:1 on --void ✓ */
:where(#storefront) ::selection { background: var(--cyan); color: var(--void); }
:where(#storefront) [hidden] { display: none !important; }

/* focus: --cyan 2px with a 2px gap. On bone the ring flips to --ink-text,
 * because --cyan on --plate is 1.39:1 (invisible) while #12181D is 15.58:1. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: var(--focus-w) solid var(--cyan);
  outline-offset: var(--focus-off);
  border-radius: var(--r-plate);
}

/* NOTE: `.chip` carries min-height:44px unconditionally (§6). There is no
 * (pointer: coarse) override, because that media feature is not reported by all
 * headless emulation and a target size that depends on it is not verifiable. */

/* ═══ 6. MOTION OFF — must win, so it is last ═══════════════════════════════
 * Transitions ≤1ms, reveals and the ticker stopped, the drawer appears with no
 * slide. transition-delay is reset too, otherwise the drawer's visibility delay
 * would keep the open state off-screen for a whole --dur after the click. */
@media (prefers-reduced-motion: reduce) {
  :root { --dur-fast: 1ms; --dur: 1ms; --dur-slow: 1ms; }
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    transition-delay: 0s !important;
    scroll-behavior: auto !important;
  }
  /* No plate transform exists to neutralise any more (removed 2026-10-15). Kept as a guard, like
     the is-fallback rule in storefront.css, so a future transform cannot silently escape the
     reduced-motion contract. */
  .plate-art img, .plate:hover .plate-art img, .plate:focus-within .plate-art img { transform: none !important; }
  #ticker { display: none !important; }   /* §3: hidden ENTIRELY, not merely paused */
}

/* ── end ─────────────────────────────────────────────────────────────────────
 * The @font-face rules live in section 0 at the top of this file and are the only
 * place a family name is bound. If a font file is ever replaced, re-run the checks
 * in docs/STOREFRONT-PALETTE.md §8 — the runtime sizes and the axis proof are
 * re-measured there, not carried over. */
