/* ==========================================================================
   DESIGN TOKENS — this is the file to edit
   ==========================================================================

   Change a number, commit, done. Nothing here needs a build step to understand.

   Colours are hardcoded, deliberately. The palette is the brand, not a
   per-store preference, so there is no admin colour picker and no runtime
   injection of these values.

   STATUS: signed off 2026-08-03.
   The design export used 57 distinct font sizes, 71 distinct paddings and 6
   border widths. This collapses them to 8 / 8 / 3. Expect a slight shift from
   the export; that is the intended tradeoff.

   When the next design revision lands, change the VALUES here. Do not add steps.
   If a revision genuinely needs a ninth size, that is a conversation, not a
   quiet append — the whole point of a scale is that it stays small.
   ========================================================================== */

:root {
  /* --- Palette. Eight values, the whole system. -------------------------- */
  --paper:        #EFE4CE;   /* page base                                    */
  --ink:          #14110F;   /* type, borders, inverted bands                */
  --accent:       #C6371B;   /* fills, rules, active states, display type    */
  --accent-text:  #A82A12;   /* the same red, darkened for small text        */
  --panel:        #E7D9BC;   /* second surface                               */
  --ink-dim:      #5A5148;   /* secondary text                               */
  --rule:         #C9BA9B;   /* hairlines, inactive borders                  */
  --rule-dark:    #3A342C;   /* rules on black                               */

  /* Two reds, because one cannot do both jobs. --accent on --paper measures
     4.20:1, under the 4.5:1 AA needs once type drops below the large-text cutoff
     of 24px, or 18.66px bold. So links, eyebrows, "Sold out" and the accent
     button's fill take --accent-text: 5.54:1 on paper, 5.01:1 on panel.
     --accent keeps the fills, hairlines and display type, where the bar is 3:1
     and 4.20 clears it, and it keeps every accent sitting on --ink, where
     darkening would take 3.55:1 down to 2.69:1. */

  --link:         var(--accent-text);
  --btn-bg:       var(--accent-text);
  --btn-fg:       var(--paper);
  --btn-bg-hover: var(--ink);

  /* --- Type scale. 8 steps, from 57 ad-hoc sizes in the export. ----------- */
  --step--1: 0.72rem;   /* 11.5px — labels, nav, captions, fine print       */
  --step-0:  0.8rem;    /* 12.8px — UI default, buttons, form fields        */
  --step-1:  1rem;      /* 16px   — body copy small                        */
  --step-2:  1.19rem;   /* 19px   — body copy                              */
  --step-3:  clamp(1.375rem, 2.6vw, 1.875rem);  /* section subheads        */
  --step-4:  clamp(1.875rem, 3.6vw, 2.875rem);  /* section heads           */
  --step-5:  clamp(2.375rem, 5vw, 3.875rem);    /* page titles             */
  --step-6:  clamp(2rem, 8.4vw, 6rem);          /* hero only               */

  /* --- Letter spacing. 3 values, from 18 in the export. ----------------- */
  --track-tight: -0.01em;
  --track-label: 0.14em;   /* uppercase mono labels */
  --track-wide:  0.22em;   /* uppercase mono eyebrows */

  /* --- Line height ------------------------------------------------------ */
  --leading-display: 0.94;
  --leading-tight:   1.15;
  --leading-body:    1.6;
  --leading-mono:    1.75;

  /* --- Spacing scale. 8 steps, from 71 paddings and 33 gaps. ------------ */
  --space-1: 0.5rem;    /*  8px */
  --space-2: 0.75rem;   /* 12px */
  --space-3: 1.125rem;  /* 18px */
  --space-4: 1.625rem;  /* 26px */
  --space-5: 2rem;      /* 32px */
  --space-6: 2.75rem;   /* 44px */
  --space-7: 4rem;      /* 64px */
  --space-8: 4.5rem;    /* 72px */

  /* --- Borders. 3 widths, down from 6 in the export. -------------------- */
  --rule-hair:  2px;   /* hairlines, inactive borders, table lines */
  --rule-frame: 4px;   /* image frames, content blocks, controls   */
  --rule-major: 6px;   /* between major sections                   */

  /* --- Layout ----------------------------------------------------------- */
  --measure:  1280px;
  --gutter:   var(--space-5);
  --gutter-sm: 1.25rem;   /* below 700px */

  /* The sticky header's rendered height, measured in Chrome at 1440px: 72.47px,
     rounded up so nothing shows through. The shop's filter bar sticks at this
     offset and focus targets scroll clear of it. base.css overrides it at 980px
     and 640px, where the header grows the MENU drawer and the wordmark wraps. */
  --header-h: 73px;

  /* --- Grain. Structural signature, do not remove. ----------------------
     Opacity is the one knob worth tuning. 0.34 to 0.38 is the design's range. */
  --grain-opacity: 0.38;

  /* --- Type families. Both faces are self-hosted; see fonts.css. --------- */
  --font-display: 'Bowlby One SC', Impact, sans-serif;
  --font-text: 'Space Mono', ui-monospace, monospace;
}

/* Breakpoints, for reference. CSS cannot tokenize these; they are literals in
   the media queries. Keep this list and the queries in sync.
     1120px  two-column layouts collapse
      980px  nav collapses into the MENU drawer
      700px  gutters shrink to --gutter-sm
      640px  wordmark wraps, cart control shrinks                            */

/*!
 * Bowlby One SC — Copyright (c) 2011 by vernon adams (vern@newtypography.co.uk),
 *   with Reserved Font Names "Bowlby" "Bowlby One" and "Bowlby One SC".
 * Space Mono — Copyright 2016 The Space Mono Project Authors
 *   (https://github.com/googlefonts/spacemono).
 * Both licensed under the SIL Open Font License 1.1: http://scripts.sil.org/OFL
 * Full license texts are in ../fonts/OFL-*.txt, alongside the font files.
 *
 * The OFL requires this notice to travel with the fonts. It rides in the
 * stylesheet because the stylesheet is the one artifact guaranteed to reach the
 * served site. Keep the OFL-*.txt files in the repo. Do not delete this block.
 */

/* Self-hosted webfonts, referenced by ABSOLUTE /fonts/ URLs on purpose.
   The font files live in this package under ../fonts/ and are copied into
   apps/storefront/public/fonts/, which Astro serves at /fonts/<filename>.
   Keeping the URLs absolute means the stylesheet contains no bundler-specific
   asset import (no `~/`, `$lib/`, `@/assets`), so it stays portable to anything
   that can serve a directory of files. If the copies drift, the site falls back
   to Impact and ui-monospace, which is visible immediately.

   Glyph coverage VERIFIED against the actual TTFs:
     BowlbyOneSC-Regular  401 glyphs
     SpaceMono            624 glyphs
   Both cover the full Spanish and French set used in product titles
   (¿ ñ á é í ó ú ü ç à è ê œ, accented capitals, middot, em dash, curly quote).
   The fallback risk flagged during design review is closed.

   font-display: swap on the display face is a deliberate tradeoff — a flash of
   Impact beats invisible headings. Revisit only if the flash tests badly. */

@font-face {
  font-family: 'Bowlby One SC';
  src: url('/fonts/BowlbyOneSC-Regular.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Space Mono';
  src: url('/fonts/SpaceMono-Regular.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Space Mono';
  src: url('/fonts/SpaceMono-Bold.ttf') format('truetype');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* Base + layout. Built against the 2026-08-05 design revision.
   Structural signatures first, then page composition.

   Values come from tokens.css. If you find yourself typing a hex or a raw px here,
   the token is missing — add it there instead. */

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-text);
  font-size: var(--step-1);
  line-height: var(--leading-body);
  -webkit-font-smoothing: antialiased;
}

/* Square corners everywhere. A rule, not a habit. The !important also catches
   anything a third-party embed drops into the page. */
* { border-radius: 0 !important; }

h1, h2, h3, .wordmark, .display {
  font-family: var(--font-display);
  text-transform: uppercase;
  line-height: var(--leading-display);
  margin: 0;
}

a { color: var(--link); text-decoration: none; }
a:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }
::selection { background: var(--accent-text); color: var(--paper); }
img { max-width: 100%; }

/* ---------- Focus ---------------------------------------------------------- */
/* The UA ring cannot be relied on here. On the ink header and footer a dark
   default ring is invisible, and .grain multiplies down whatever is painted
   under it. Measured through the grain at its darkest pixel (see the note on
   .grain below): an ink ring on paper holds 9.18:1 and a paper ring on ink the
   same, where an accent ring would fall to 2.59:1 on paper and 2.50:1 on ink —
   under the 3:1 that WCAG 1.4.11 asks of a focus indicator. So the ring is ink,
   flipped to paper on the inverted surfaces.
   The 3px offset is load-bearing: it leaves a gap of page background between the
   ring and any filled control, which is what keeps the ring legible on one.
   Gap-to-fill measures 9.18:1 over an ink fill and 3.41:1 over --accent-text.
   Ceiling: the list is explicit, so a new kind of focusable element has to be
   added to it. A bare :focus-visible would also ring the #content skip target,
   which is a wrapper rather than a control. */
a:focus-visible,
button:focus-visible,
summary:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:not([tabindex="-1"]):focus-visible {
  outline: 3px solid var(--ink);
  outline-offset: 3px;
}
.site-header :focus-visible,
.site-footer :focus-visible,
.section--ink :focus-visible { outline-color: var(--paper); }

/* Focus targets clear the sticky header instead of landing underneath it
   (WCAG 2.4.11). --header-h tracks the header per breakpoint.
   Ceiling: on /shop the filter bar sticks below the header and can still cover
   part of a focused card. Clearing that too would mean a 384px offset at 390px
   (a measured 154px header plus a 229px filter bar, whose chips wrap onto four
   rows), which buries the card it is meant to reveal. Partial overlap is what
   2.4.11 permits; total obscuring is what it forbids. */
a, button, summary, input, select, textarea, [tabindex] {
  scroll-margin-top: calc(var(--header-h) + var(--space-3));
}

/* Monospace label: the design's third voice, after display and body. */
.label, .eyebrow, .site-nav, .cart-link, .filters, .fine-print, .meta {
  font-family: var(--font-text);
  text-transform: uppercase;
  letter-spacing: var(--track-label);
}
.eyebrow {
  font-size: var(--step--1);
  letter-spacing: var(--track-wide);
  color: var(--accent-text);
  font-weight: 700;
}

/* Paper grain. Fixed multiply layer above the page. Two known costs:
   compositing on scroll over a fixed layer, and z-index 60 sitting above
   everything, so any overlay rendered above the page picks up a grey cast. */
.grain {
  position: fixed;
  inset: 0;
  z-index: 60;
  pointer-events: none;
  mix-blend-mode: multiply;
  opacity: var(--grain-opacity);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='220' height='220'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='220' height='220' filter='url(%23n)' opacity='0.55'/></svg>");
}

/* ---------- Shared shell ---------------------------------------------------- */

.wrap { max-width: var(--measure); margin: 0 auto; padding-left: var(--gutter); padding-right: var(--gutter); }

/* Major section breaks. The 6px rule is a structural signature, not decoration. */
.section { border-bottom: var(--rule-major) solid var(--ink); }
.section--panel { background: var(--panel); }
.section--ink { background: var(--ink); color: var(--paper); }
.section--ink a { color: var(--paper); }

/* ---------- Header --------------------------------------------------------- */

.site-header {
  position: sticky; top: 0; z-index: 50;
  background: var(--ink); color: var(--paper);
  border-bottom: var(--rule-frame) solid var(--ink);
}
.site-header > .wrap {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1.75rem;
  padding-top: 0.875rem; padding-bottom: 0.875rem;
}
.site-header a { color: var(--paper); }

.wordmark {
  min-width: 0; white-space: nowrap;
  font-size: 1.1875rem; letter-spacing: 0.04em; line-height: 1;
  display: flex; align-items: center; gap: 0.625rem;
}
/* The rotated star and the boxed last word are the wordmark's whole identity.
   Do not straighten them. */
.wordmark .star {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px;
  background: var(--accent); color: var(--paper);
  font-size: 15px; transform: rotate(-6deg);
}
.wordmark .boxed {
  display: inline-block;
  background: var(--accent); color: var(--paper);
  padding: 1px 8px 3px;
  transform: rotate(-3.5deg);
}

.site-nav { display: flex; align-items: center; gap: 2.125rem; white-space: nowrap; font-size: var(--step-0); }
/* Every link reserves the underline; only the current one colours it in. Applying the border to
   `[aria-current]` alone made that link 5px taller, and `align-items: center` then pushed its
   text ~2.5px above every other item in the row. */
.site-nav a { border-bottom: 3px solid transparent; padding-bottom: 2px; }
.site-nav a[aria-current] { border-bottom-color: var(--accent); }

.header-end { flex-shrink: 0; display: flex; align-items: center; gap: 1rem; font-size: var(--step-0); }
.cart-link { border: var(--rule-hair) solid var(--paper); padding: 0.5rem 0.875rem; }

.mobile-menu { display: none; border-top: var(--rule-hair) solid var(--rule-dark); }
.mobile-menu summary {
  list-style: none; cursor: pointer;
  padding: 0.8rem var(--gutter-sm);
  font-size: var(--step-0); letter-spacing: 0.18em; text-transform: uppercase;
  display: flex; align-items: center; justify-content: space-between;
}
.mobile-menu summary::-webkit-details-marker { display: none; }
.mobile-menu .bars { display: flex; flex-direction: column; gap: 3.5px; width: 19px; }
.mobile-menu .bars span { display: block; height: 2.5px; background: var(--accent); }
.mobile-menu nav {
  display: flex; flex-direction: column;
  padding: 4px var(--gutter-sm) var(--gutter-sm);
  font-size: 0.875rem; letter-spacing: 0.12em; text-transform: uppercase;
}
.mobile-menu nav a { padding: 0.75rem 0; border-top: 1px solid var(--rule-dark); }

/* ---------- Buttons ------------------------------------------------------- */

/* No bare `button[type="submit"]` here. It reads (0,1,1) and so beat `.btn--accent` at
   (0,1,0) regardless of source order, which meant every primary CTA on the site — Checkout,
   Add to cart, and the card's hover Add — rendered ink instead of the vermilion that
   design/DESIGN-SYSTEM.md names as the accent's job. Every submit button in the storefront
   carries `.btn` or `.pcard-add`, so the attribute selector was buying nothing.
   Ceiling: a new submit button with neither class gets no styling at all, which is loud
   enough to notice. */
.btn, .cta {
  display: inline-block;
  font-family: var(--font-text); font-weight: 700;
  font-size: var(--step-0); letter-spacing: 0.18em; text-transform: uppercase;
  padding: var(--space-3) var(--space-6);
  border: 3px solid var(--ink);
  background: var(--ink); color: var(--paper);
  cursor: pointer; text-align: center;
}
/* Hover fills with --accent-text, not --accent: the label is --paper at 12.8px,
   so the hover state has the same 4.5:1 obligation as the resting one. */
.btn:hover, .cta:hover {
  background: var(--accent-text); border-color: var(--accent-text); color: var(--paper);
  text-decoration: none;
}
.btn--accent { background: var(--btn-bg); border-color: var(--btn-bg); color: var(--btn-fg); }
.btn--accent:hover { background: var(--btn-bg-hover); border-color: var(--btn-bg-hover); }
.btn[disabled] { background: var(--rule); border-color: var(--ink-dim); color: var(--ink-dim); cursor: not-allowed; }

.chip {
  font-family: var(--font-text);
  font-size: var(--step-0); letter-spacing: var(--track-label); text-transform: uppercase;
  padding: 0.625rem 1.125rem;
  border: 3px solid var(--ink);
  background: transparent; color: var(--ink);
  cursor: pointer;
}
.chip[aria-pressed="true"], .chip--on { background: var(--ink); color: var(--paper); }

/* ---------- Hero ---------------------------------------------------------- */

.hero > .wrap {
  display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  gap: var(--space-7); align-items: center;
  padding-top: var(--space-8); padding-bottom: var(--space-7);
}
.hero h1 {
  font-size: var(--step-6);
  line-height: 0.88; letter-spacing: var(--track-tight);
  overflow-wrap: normal; hyphens: none; text-wrap: balance;
  margin-bottom: 0.5rem;
}
/* Each line sits at its own slight angle, like separately pressed blocks. */
.hero h1 .l1 { display: block; transform: rotate(-0.7deg); }
.hero h1 .l2 { display: block; transform: rotate(0.4deg) translateX(4px); }
.hero h1 .l3 { display: inline-block; background: var(--accent); color: var(--paper); padding: 2px 16px 8px; transform: rotate(-2.5deg); }
.hero .tagline {
  font-family: var(--font-display); font-size: 1.4375rem;
  line-height: 1.25; text-transform: uppercase; letter-spacing: 0.01em;
  max-width: 22ch; margin: var(--space-4) 0 var(--space-3);
  transform: rotate(-0.3deg);
}
.hero .hero-line { font-size: var(--step-2); line-height: 1.5; max-width: 46ch; margin: 0 0 var(--space-6); text-wrap: pretty; }
.hero-art { display: grid; place-items: center; }
.hero-art img { display: block; width: 100%; height: auto; transform: rotate(-1.5deg); }

/* ---------- Full-bleed statement band ------------------------------------- */

.band > .wrap {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: var(--space-4);
  padding-top: var(--space-6); padding-bottom: var(--space-7);
}
.band p {
  margin: 0; font-family: var(--font-display);
  font-size: var(--step-4); line-height: 1.14; text-transform: uppercase;
  width: 100%; text-wrap: pretty;
}
.band .more { font-size: var(--step-0); font-weight: 700; letter-spacing: 0.18em; text-transform: uppercase; border-bottom: var(--rule-hair) solid var(--accent); padding-bottom: 3px; }

/* ---------- Section heading row ------------------------------------------ */

.head-row {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-4); flex-wrap: wrap;
  margin-bottom: var(--space-6);
  padding-bottom: var(--space-3);
  border-bottom: var(--rule-frame) solid var(--ink);
}
.head-row h1, .head-row h2 { font-size: var(--step-4); transform: rotate(-0.4deg); }
.head-row .more { font-size: var(--step-0); font-weight: 700; letter-spacing: 0.18em; text-transform: uppercase; white-space: nowrap; }

/* ---------- Flagship block ----------------------------------------------- */

.flagship-block {
  display: grid; grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  gap: var(--space-6); align-items: center;
  border: var(--rule-frame) solid var(--ink);
  background: var(--panel);
  padding: var(--space-5);
  margin-bottom: var(--space-6);
}
.flagship-block h2, .flagship-block h3 { font-size: var(--step-5); line-height: 0.95; margin-bottom: var(--space-3); }
.flagship-block p { font-size: 1.125rem; line-height: 1.55; max-width: 52ch; margin: 0 0 var(--space-4); text-wrap: pretty; }
.flagship-block .facts { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; margin-bottom: var(--space-4); }
.pill { border: var(--rule-hair) solid var(--ink); padding: 0.375rem 0.75rem; font-size: var(--step-0); letter-spacing: var(--track-label); text-transform: uppercase; }

/* ---------- Masonry product grid ----------------------------------------- */
/* CSS columns, not grid. The design lets tall and short prints interlock rather
   than forcing a uniform row height, which matters when aspect ratios come from
   the artwork itself. */

.masonry { columns: 3; column-gap: var(--space-5); }
.masonry > * { break-inside: avoid; margin: 0 0 var(--space-5); }

.pcard { display: flex; flex-direction: column; gap: 0.8rem; }
.pcard-media { position: relative; }
/* :not(.pcard-add) is the whole fix for the empty box on hover. This rule frames the artwork,
   and it also matched the "Choose options" anchor, which is a sibling `> a`. At (0,1,1) it beat
   `.pcard-add` at (0,1,0) and applied `background-color: var(--paper)` over paper-coloured text,
   so the control was a blank framed box until a direct hover turned the text ink. Same
   specificity trap as the accent button. */
.pcard-media > a:not(.pcard-add) {
  display: block;
  border: var(--rule-frame) solid var(--ink);
  background-color: var(--paper);
  background-size: contain; background-position: center; background-repeat: no-repeat;
}
.pcard-title { font-family: var(--font-display); font-size: 1.3125rem; line-height: 1.12; text-transform: uppercase; color: var(--ink); }
.pcard-title:hover { color: var(--accent); text-decoration: none; }
.pcard-meta {
  display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
  font-size: var(--step-0); letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-dim);
}
.pcard-price { font-weight: 700; font-size: 0.9375rem; letter-spacing: 0.02em; color: var(--ink); border-bottom: 3px solid var(--accent); padding-bottom: 1px; }
.pcard .sold { color: var(--accent-text); font-weight: 700; }

/* Quick-pick: one control per option value, for a single-axis product with few values. Stacked
   at the foot of the card in the same place a single Add button would sit. Each is a real form
   post, so it works with JavaScript blocked. */
.pcard-options {
  position: absolute; left: 12px; right: 12px; bottom: 12px;
  display: flex; flex-direction: column; gap: 6px;
  opacity: 0; transform: translateY(8px);
  transition: opacity 0.14s ease, transform 0.14s ease;
}
.pcard-options .pcard-add {
  position: static; left: auto; right: auto; bottom: auto;
  opacity: 1; transform: none;
  padding: 0.625rem 0.5rem;
  font-size: var(--step--1); letter-spacing: 0.1em;
}
/* focus-within, not just hover: the forms are keyboard-reachable and a control you can focus but
   cannot see is worse than one that is simply absent. */
.pcard:hover .pcard-options,
.pcard:focus-within .pcard-options { opacity: 1; transform: none; }
@media (hover: none) { .pcard-options { opacity: 1; transform: none; } }

/* Hover-reveal add control. Hidden by opacity rather than display so it can be
   focused by keyboard, and forced visible where hover does not exist. */
.pcard-add {
  position: absolute; left: 12px; right: 12px; bottom: 12px;
  font-family: var(--font-text); font-weight: 700;
  font-size: var(--step-0); letter-spacing: 0.16em; text-transform: uppercase;
  padding: 0.875rem 0.625rem;
  border: 3px solid var(--ink);
  background: var(--accent-text); color: var(--paper);
  /* Stated rather than inherited from the .btn rule, which no longer matches this: the
     "Choose options" variant is an <a>, and an anchor does not centre its own text. */
  cursor: pointer; text-align: center;
  opacity: 0; transform: translateY(8px);
  transition: opacity 0.14s ease, transform 0.14s ease;
}
.pcard:hover .pcard-add,
.pcard:focus-within .pcard-add,
.pcard-add:focus-visible { opacity: 1; transform: none; }
@media (hover: none) { .pcard-add { opacity: 1; transform: none; } }

/* ---------- Shop heading + filter bar ------------------------------------ */

.shop-heading > .wrap { padding-top: var(--space-7); padding-bottom: var(--space-6); }
.shop-heading h1 { font-size: var(--step-5); line-height: 0.9; transform: rotate(-0.5deg); }
.shop-heading .intro { font-size: var(--step-2); line-height: 1.5; max-width: 56ch; margin: var(--space-4) 0 0; text-wrap: pretty; }

/* Sticks directly below the header, so the offset is the header's own height.
   The 63px literal this replaces was never the rendered height: Chrome measures
   72.47px at 1440px, so the bar sat 9px inside the header, and at 390px the
   header is 154px, which buried the whole chip row behind it. */
.filters {
  position: sticky; top: var(--header-h); z-index: 40;
  background: var(--panel);
  border-bottom: var(--rule-major) solid var(--ink);
}
.filters > .wrap {
  display: flex; align-items: center; gap: var(--space-6); flex-wrap: wrap;
  padding-top: var(--space-4); padding-bottom: var(--space-4);
}
.filter-group { display: flex; align-items: center; gap: 0.875rem; flex-wrap: wrap; }
.filter-group > .label { font-size: var(--step--1); letter-spacing: var(--track-wide); font-weight: 700; }
.filter-count { margin-left: auto; font-size: var(--step-0); letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-dim); white-space: nowrap; }

/* ---------- Product page ------------------------------------------------- */

.pdp > .wrap {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-7); align-items: start;
  padding-top: var(--space-6); padding-bottom: var(--space-8);
}
.primary-image { display: block; width: 100%; height: auto; border: var(--rule-frame) solid var(--ink); background: var(--paper); }

.pdp h1 { font-size: var(--step-5); margin-bottom: var(--space-3); }
.pdp .price { font-family: var(--font-display); font-size: 2.75rem; line-height: 1; margin-bottom: var(--space-5); }
.pdp .description { margin-top: var(--space-5); }
.pdp .description p { font-size: 1.125rem; line-height: 1.65; max-width: 68ch; text-wrap: pretty; }
/* Long-form product history: paragraphs plus pulled quotes. The description
   arrives as one HTML blob, so the container is the only styling hook. */
.pdp .description blockquote {
  margin: var(--space-4) 0; padding: 4px 0 4px var(--space-4);
  border-left: 5px solid var(--accent);
}
.pdp .description blockquote p { font-family: var(--font-display); font-size: 1.3125rem; line-height: 1.45; text-transform: none; }
.pdp .unavailable { font-family: var(--font-display); font-size: 1.5rem; text-transform: uppercase; color: var(--accent); }

.option-axis { border: 0; padding: 0; margin: 0 0 var(--space-4); }
.option-axis legend { font-size: var(--step--1); letter-spacing: var(--track-wide); font-weight: 700; text-transform: uppercase; padding: 0; margin-bottom: 0.875rem; }
.option-axis > div { display: flex; gap: 0.75rem; flex-wrap: wrap; }
.option-axis button {
  font-family: var(--font-text); font-size: var(--step-1);
  letter-spacing: var(--track-label); text-transform: uppercase;
  min-width: 58px; padding: 0.875rem 1.5rem;
  border: 3px solid var(--ink); background: transparent; color: var(--ink); cursor: pointer;
}
.option-axis button[aria-pressed="true"] { background: var(--ink); color: var(--paper); }

/* ---------- Cart --------------------------------------------------------- */

.cart-split {
  display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 0.9fr);
  gap: var(--space-6); align-items: start;
  padding-top: var(--space-6); padding-bottom: var(--space-8);
}
.cart-items { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: var(--space-4); }
.cart-items > li {
  display: grid; grid-template-columns: 112px minmax(0, 1fr) auto;
  gap: var(--space-4); align-items: start;
  padding-bottom: var(--space-4); border-bottom: var(--rule-hair) solid var(--rule);
}
.cart-items img { display: block; width: 100%; border: 3px solid var(--ink); }
/* Offset off the header, like .filters. The 96px literal this replaces held at
   1440px only by luck: the header measures 72.47px there, so it parked the panel
   23px lower than it needed to sit. It would have been 25px short of the 121px
   header below 980px, which does not bite today only because .cart-split is one
   column under 1120px and a single-column grid area leaves sticky nothing to
   travel through. --space-4 keeps roughly the gap the literal produced. */
.order-summary {
  border: var(--rule-frame) solid var(--ink); background: var(--panel);
  padding: var(--space-5); position: sticky; top: calc(var(--header-h) + var(--space-4));
}
.order-summary > div { display: flex; justify-content: space-between; gap: 1rem; font-size: var(--step-1); margin-bottom: 0.75rem; }
.cart-empty { border: var(--rule-frame) dashed var(--ink); padding: var(--space-6) var(--space-5); text-align: center; }
.cart-empty h1 { font-size: 1.625rem; margin-bottom: 0.75rem; }

/* ---------- Forms -------------------------------------------------------- */

label { display: block; font-size: var(--step--1); letter-spacing: var(--track-label); text-transform: uppercase; font-weight: 700; margin-bottom: 0.5rem; }
input[type="text"], input[type="email"], input[type="number"], select, textarea {
  width: 100%; font-family: var(--font-text); font-size: var(--step-1);
  padding: var(--space-3); background: var(--paper);
  border: 3px solid var(--ink); color: var(--ink);
  margin-bottom: var(--space-3);
}
.errors { list-style: none; padding: var(--space-3); margin: 0 0 var(--space-4); border: 3px solid var(--accent); color: var(--accent-text); }

/* ---------- Footer ------------------------------------------------------- */

.site-footer { background: var(--ink); color: var(--paper); }
.site-footer a { color: var(--paper); }
.footer-cols {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-6);
  padding-top: var(--space-7); padding-bottom: var(--space-6);
}
.footer-cols h2 { font-family: var(--font-text); font-size: var(--step--1); letter-spacing: var(--track-wide); color: var(--accent); font-weight: 700; margin-bottom: 1rem; }
.footer-cols nav { display: flex; flex-direction: column; gap: 0.7rem; font-size: var(--step-1); }
/* white-space is the load-bearing part. `.wordmark` sets `nowrap` because the header wordmark
   must never break, and the footer override reset display and size but not that — so a
   four-word store name at 1.5rem ran ~950px wide inside a 200px grid column and painted over
   the next column's heading. */
.site-footer .wordmark {
  font-family: var(--font-display); font-size: 1.5rem; line-height: 1.05;
  display: block; white-space: normal; text-wrap: balance;
  margin-bottom: 1rem;
}
.site-footer .tagline { font-size: var(--step-0); line-height: 1.8; opacity: 0.8; margin: 0; }
.footer-base {
  border-top: var(--rule-hair) solid var(--rule-dark);
  display: flex; justify-content: space-between; gap: var(--space-4); flex-wrap: wrap;
  padding-top: var(--space-4); padding-bottom: var(--space-6);
  font-size: var(--step--1); letter-spacing: 0.08em; opacity: 0.7;
}

/* ---------- Editorial pages ---------------------------------------------- */
/* About, Artists and anything later share one narrow-measure treatment. The
   layout puts `custom` on <body> for these; the hooks are plain elements inside
   .site-main, so authored prose needs no per-page classes.

   The page has to supply the element. Base.astro owns the one main landmark and
   emits no .site-main of its own, so these rules are silently conditional on the
   page carrying class="site-main wrap" — on a div, never a second main.
   about.astro does. Anything else using bodyClass="custom" must too. */

body.custom .site-main { max-width: 72ch; }
body.custom .site-main h2 { font-size: var(--step-4); margin: var(--space-7) 0 var(--space-4); padding-bottom: var(--space-3); border-bottom: var(--rule-frame) solid var(--ink); }
body.custom .site-main h3 { font-size: var(--step-3); margin: var(--space-5) 0 var(--space-3); }
body.custom .site-main p { font-size: var(--step-2); line-height: 1.6; margin: 0 0 var(--space-4); text-wrap: pretty; }
body.custom .site-main blockquote { margin: var(--space-4) 0; padding-left: var(--space-4); border-left: 5px solid var(--accent); }

/* ---------- Responsive --------------------------------------------------- */
/* Breakpoints are literals; CSS cannot tokenize them. Keep in step with the
   comment at the bottom of tokens.css. */

@media (max-width: 1120px) {
  .hero > .wrap,
  .flagship-block,
  .pdp > .wrap,
  .cart-split { grid-template-columns: minmax(0, 1fr); gap: var(--space-6); }
}

@media (max-width: 1080px) { .masonry { columns: 2; } }

@media (max-width: 980px) {
  .site-nav { display: none; }
  .mobile-menu { display: block; }
  .site-header > .wrap { flex-wrap: wrap; gap: 0.75rem; padding-left: var(--gutter-sm); padding-right: var(--gutter-sm); }
  .wordmark { font-size: 0.875rem; gap: 7px; }
  /* The MENU drawer's summary row lands here, taking the header from 72.47px to
     a measured 120.53px across 641-980px. */
  :root { --header-h: 121px; }
}

@media (max-width: 720px) { .masonry { columns: 1; } }

@media (max-width: 700px) { :root { --gutter: var(--gutter-sm); } }

@media (max-width: 640px) {
  .wordmark { white-space: normal; font-size: 0.78rem; line-height: 1.12; }
  .cart-link { padding: 7px 10px; font-size: var(--step--1); }
  .cart-items > li { grid-template-columns: 72px minmax(0, 1fr); }
  /* The wordmark is free to wrap here, so the header's height follows the
     viewport: measured 116.48px at 480-640px, 154.48px at 390px, 160.42px at
     320px. This is the tallest of those, which is what focus targets have to
     clear. Below 640px only scroll-margin-top reads it, since the filter bar
     goes static on the next line, so overshooting costs a little extra
     clearance and nothing else. */
  :root { --header-h: 161px; }
  /* Sticky is wrong at this width. The chips wrap onto four rows, so the bar
     measures 229.41px at 390px; pinned under a 154px header, the two would hold
     383px of an 844px screen — 45% of the viewport — before the first product. */
  .filters { position: static; }
  /* The line's third child — line total plus the Remove form — auto-placed into
     the 72px thumbnail track at row 2, where the 155px button overhung it by
     83px. Sending it to the text column puts it under the title instead. */
  .cart-items > li > div:last-of-type { grid-column: 2; }
  .order-summary { position: static; }
}

/* Visually hidden but reachable by screen readers. Used where the design shows a control
   with no visible label. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Modifier for the skip link: hidden on the same terms as .sr-only until it takes
   focus, then it paints itself over the header. z-index 61 clears the sticky
   header at 50 and .grain at 60, so the first control a keyboard user meets is
   the one thing on the page the grain does not dim. Offsets are logical, so it
   flips with the writing direction. */
.sr-only--focusable:focus {
  width: auto; height: auto;
  margin: 0; padding: var(--space-2) var(--space-3);
  overflow: visible; clip: auto;
  top: 0; inset-inline-start: var(--gutter);
  z-index: 61;
  background: var(--ink); color: var(--paper);
  font-size: var(--step-0); letter-spacing: var(--track-label); text-transform: uppercase;
  outline-color: var(--paper);
}


h2 {
  font-size: var(--step-1);
  line-height: 1.15;
  margin-bottom: var(--space-3);
}
h1 {
  font-size: var(--step-3);
  line-height: 1.05;
}
