/* "The Ledger" design tokens, one :root block. Contrast verified by
   scripts/check-contrast.mjs - re-run it after any colour change.

   THREE VALUES DELIBERATELY DEPART FROM THE HANDOFF, all for 1.4.3/1.4.11:

     ink-faint  #8A7F6E -> #71685A   3.55:1 on paper, and it carries real
                                     metadata text (department under a brand
                                     name, the PDP city line) at 11-12px.
     rule-chip  #C9BDA6 -> #8D8575   1.68:1, and it is the ONLY thing marking
                                     the edge of an unselected option button.
                                     A control boundary needs 3:1.
     disabled   #B0A692 -> #6F695C   2.18:1. Out-of-stock values are still
                                     real links here, so the "inactive
                                     control" exemption does not apply.

   The purely decorative hairlines (rule, rule-soft) stay exactly as designed:
   they separate rows and never identify a control, so 1.4.11 does not reach
   them.

   LIGHT ONLY, on purpose. The handoff specifies one paper-and-ink palette and
   no dark variant, and the dark "Where they are" band only reads as a tonal
   pivot because every other section is light. Inverting the page would destroy
   the device. A dark theme needs its own design pass, not a derivation. */
:root {
  --paper: #F7F3EA;
  --paper-panel: #EFE9DC;
  --paper-chip: #F1EBDE;
  --hover-wash: #EFE7D7;

  --ink: #1B1712;
  --ink-body: #453D34;
  --ink-muted: #6B6154;
  --ink-faint: #6F665A;   /* was #8A7F6E - see note above */

  --brick: #9E3B26;
  --brick-light: #C9714F; /* on dark backgrounds ONLY */

  --rule: #D5CAB4;        /* decorative hairline */
  --rule-soft: #E1D8C6;   /* decorative row separator */
  --rule-chip: #898173;   /* control boundary - was #C9BDA6 */

  --dark-panel: #241F18;
  --dark-rule: #3B342A;
  --dark-rule-strong: #4A4133;
  --cream: #E5DCCA;
  --cream-strong: #F2E9D6;
  --cream-muted: #C4B9A4;

  --in-stock: #2F6141;
  --disabled: #6B6558;    /* was #B0A692, then #6F695C: 4.44:1 on --hover-wash,
                             which is the state the chip is in while its focus
                             ring is showing */
  --placeholder-a: #E7DFCD;
  --placeholder-b: #EDE6D6;
  --placeholder-lg-a: #E4DBC7;
  --placeholder-lg-b: #EAE2D1;

  /* Focus is TWO scoped tokens, not one hue. The handoff's #1D4ED8 is 2.66:1
     on --ink, so it vanishes on the dark band, and no outline-offset rescues
     ink-on-ink. No single hue clears 3:1 on paper, ink AND brick, so the ring
     takes its colour from whatever surface it lands on.

     Both must stay literal hex. check-contrast.mjs matches `--name: #hex;`
     only, so `var(--ink)` here would read as a missing token and silently
     un-gate every focus pair. */
  --focus: #1B1712;          /* 16.10:1 on paper */
  --focus-on-dark: #F2E9D6;  /* 14.77:1 on ink, 5.60:1 on brick */
  --on-accent: #F7F3EA;

  /* Decoration only: the star between the two utility-bar phrases. Flat, not
     opacity - the handoff applies opacity to the whole row, which drags the
     TEXT down to 3.69:1 with it. This is the exact flattening of #F4E9DC at
     40% over brick, so the look is unchanged. */
  --utility-star: #C0816F;

  --page: 1440px;
  --pad: 40px;
  --font-ui: Archivo, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
  --font-serif: Newsreader, ui-serif, Georgia, "Times New Roman", serif;
  --font-slab: Bevan, ui-serif, Georgia, serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;

  /* Legacy aliases: components not yet rebuilt onto The Ledger still read
     these. Delete each one as its component is converted. */
  --sand: var(--paper-panel);
  --ink-soft: var(--ink-body);
  --rust: var(--brick);
  --rust-dark: #7E2E1E;
  --line: var(--rule-chip);
  --gap: 1.25rem;
  --radius: 0;
  --header-h: 4rem;
  --font: var(--font-ui);

  color-scheme: light;
}

/* Self-hosted so the page never depends on a third party, and so no visitor
   request leaks to Google. Archivo and Newsreader are variable fonts, so the
   whole weight range is one file each. */
@font-face {
  font-family: Bevan; font-style: normal; font-weight: 400; font-display: swap;
  src: url("/fonts/bevan-400.woff2") format("woff2");
}
@font-face {
  font-family: Newsreader; font-style: normal; font-weight: 300 700; font-display: swap;
  src: url("/fonts/newsreader-var.woff2") format("woff2");
}
@font-face {
  font-family: Newsreader; font-style: italic; font-weight: 300 500; font-display: swap;
  src: url("/fonts/newsreader-var-italic.woff2") format("woff2");
}
@font-face {
  font-family: Archivo; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("/fonts/archivo-var.woff2") format("woff2");
}

*, *::before, *::after { box-sizing: border-box; }
/* Guard: the shelf's buttons ship hidden until shelf.js wires them, and any
   future display rule on their class would silently defeat the low-specificity
   UA [hidden] rule - 24 visible dead buttons per page with JS off. */
[hidden] { display: none !important; }
/* clip, not hidden: `overflow: hidden` on the root makes the element a scroll
   container, which silently kills position:sticky and lets a stray wide child
   scroll rather than be clipped. */
html, body { overflow-x: clip; }
html { -webkit-text-size-adjust: 100%; scroll-padding-top: calc(var(--header-h) + 1rem); }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font);
  font-size: 1rem;
  line-height: 1.55;
}

img { max-width: 100%; height: auto; display: block; }

/* The Ledger is drawn on a 1440px canvas with 40px gutters, and the eight nav
   cells only fit their labels at that width. The old 68rem container was the
   pre-redesign value, now deleted along with its last consumer. */
.wrap { width: 100%; max-width: var(--page); margin-inline: auto; padding-inline: var(--pad); }
@media (max-width: 48rem) { .wrap { padding-inline: 1.25rem; } }

/* Focus, guarded so engines without :focus-visible keep a ring. */
:focus { outline: 3px solid var(--focus); outline-offset: 2px; }
@supports selector(:focus-visible) {
  :focus { outline: none; }
  :focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
}
main[tabindex="-1"]:focus { outline: none; }

/* Never display:none or width:0 - both drop it from the accessible name. */
.u-visually-hidden {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%);
  white-space: nowrap;
}

.skip {
  position: fixed; top: 0; inset-inline-start: 0; z-index: 1000;
  transform: translateY(-110%);
  display: inline-flex; align-items: center; min-height: 44px;
  padding: 0.5rem 1.25rem;
  background: var(--ink); color: var(--paper);
  text-decoration: underline;
}
/* INSET, and cream, which inverts the old rule here. The old rule existed
   because the blue ring was 2.66:1 on this ink fill, so it had to be pushed
   off onto the page behind. Two things changed: --focus is now ink itself
   (1.00:1 on this fill), and the topmost strip behind a focused skip link is
   now brick, where an ink ring is 2.64:1. A cream ring drawn INSIDE the link's
   own fill is 14.77:1 and does not care what sits behind it. */
.skip:focus { transform: translateY(0); outline: 3px solid var(--focus-on-dark); outline-offset: -3px; }

/* --- consent banner ------------------------------------------------------- */

/* Fixed to the viewport bottom (owner's call), with the buy bar's two
   mitigations so it never traps anything behind it: the page reserves space
   at its end, and scroll-padding-bottom keeps a focused element from parking
   underneath. Renders only until the visitor answers, so all of this
   disappears after one click. */
.consent {
  position: fixed; inset: auto 0 0 0; z-index: 90;
  background: var(--paper-chip); border-top: 1.5px solid var(--ink);
}
/* Reserve enough document end that the footer is fully readable pre-consent.
   :has, so the reserve exists exactly when the banner does. */
body:has(.consent) { padding-block-end: 8rem; }
html:has(.consent) { scroll-padding-bottom: 9rem; }
@media (max-width: 48rem) {
  body:has(.consent) { padding-block-end: 15rem; }
  html:has(.consent) { scroll-padding-bottom: 16rem; }
  /* The PDP buy bar also pins to the bottom. Pre-consent the banner wins the
     spot (it leaves after one answer, and the in-flow buy link above the fold
     is the second copy that exists for exactly this kind of moment). */
  body:has(.consent) .buybar { display: none; }
}
.consent__inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 0.75rem 2rem; flex-wrap: wrap; padding-block: 0.875rem;
}
.consent__copy { margin: 0; font-size: 0.875rem; max-width: 62ch; color: var(--ink-body); }
.consent__copy a { color: var(--brick); text-decoration: underline; text-underline-offset: 0.15em; }
.consent__form { display: flex; gap: 0.625rem; flex-wrap: wrap; }
/* Equal visual weight on purpose: neither choice gets to be the easy one. */
.consent__form .btn { font-size: 0.75rem; letter-spacing: 0.11em; text-transform: uppercase; }

/* --- utility strip -------------------------------------------------------- */

/* Anything focusable that lands in here needs the cream ring: an ink ring on
   brick is 2.64:1. Scoping the token rather than the rule means the single
   global :focus-visible declaration keeps working untouched. */
.utility {
  --focus: var(--focus-on-dark);
  background: var(--brick); color: var(--on-accent);
  font-size: 0.6875rem; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase;
}
.utility__inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 0.5rem 1.375rem; flex-wrap: wrap; padding-block: 0.5625rem;
}
.utility__claim { margin: 0; }
/* display:none, not a clip: the hidden variant must leave the accessibility
   tree entirely or the claim is announced twice. */
.utility__claim--short { display: none; }
@media (max-width: 48rem) {
  .utility__claim--long { display: none; }
  .utility__claim--short { display: inline; }
}
.utility__meta { display: flex; align-items: center; gap: 1.375rem; flex-wrap: wrap; list-style: none; margin: 0; padding: 0; }
/* The separator is drawn, not typed, per the standing rule - but U+2605 is a
   SYMBOL with a spoken name ("black star"), not punctuation like the middot in
   .hero__stats, so it gets an empty alt string. aria-hidden is unavailable:
   the host <li> carries real text. */
.utility__meta li + li::before {
  content: "\2605" / ""; margin-inline-end: 1.375rem; color: var(--utility-star);
}
@media (max-width: 48rem) {
  /* One centred line, as drawn. The ships line is the part that goes, not
     the claim. */
  .utility__inner { justify-content: center; padding-block: 0.5rem; }
  .utility__meta { display: none; }
}

/* --- masthead ------------------------------------------------------------- */

.masthead { background: var(--paper); }
.masthead__row {
  display: flex; align-items: baseline; flex-wrap: wrap;
  gap: 0.5rem 1rem; padding-block: 1.625rem 0;
}
.masthead__tagline { font-family: var(--font-serif); font-style: italic; font-size: 1rem; color: var(--ink-muted); }
/* Hide the tagline before the tools collide with it. 48rem is too late:
   wordmark + italic + 18rem field + two links wrap as orphans. */
@media (max-width: 60rem) { .masthead__tagline { display: none; } }
/* Search, All brands and Shelf wrap as one unit. Find grows inside the
   group so the wide-canvas gap is the field, not empty paper. Align to
   the end so the three underlines share one baseline. */
.masthead__tools {
  display: flex; align-items: flex-end; flex-wrap: nowrap;
  gap: 0.75rem 0.875rem;
  margin-inline-start: auto;
  flex: 1 1 16rem; min-width: 0;
}

.wordmark {
  display: inline-flex; align-items: center; min-height: 44px;
  font-family: var(--font-slab); font-size: 1.9375rem; line-height: 1;
  letter-spacing: 0.005em; color: var(--ink); text-decoration: none;
}
/* No underline on hover. The house rule that text links must underline covers
   inline links in prose; a wordmark is a logo, and underlining it makes it read
   as a link in a sentence rather than as the masthead. It is still an <a> to
   "/" and it still takes the standard focus ring, so nothing is lost for the
   keyboard - only the pointer affordance changes, to a colour shift. */
.wordmark:hover { color: var(--brick); }
@media (max-width: 48rem) { .wordmark { font-size: 1.3125rem; } }

/* Underline-only field, as drawn. The handoff sets `outline: none` on the
   input; that is refused outright. The ring goes on the wrapper instead, which
   is barely wider than the field, so it still reads as the field's ring. */
.find { display: flex; align-items: center; gap: 0.625rem; flex: 1 1 12rem; min-width: 0; padding: 0 2px 6px; border-bottom: 1.5px solid var(--ink); }
.find:focus-within { outline: 3px solid var(--focus); outline-offset: 2px; }
/* Narrow it to the INPUT where :has is available. The wrapper ring exists only
   because the input has no box of its own to draw on; the Search button has
   one, so a bare :focus-within double-rings it. */
@supports selector(:has(*)) {
  .find:focus-within { outline: none; }
  .find:has(.find__input:focus-visible) { outline: 3px solid var(--focus); outline-offset: 2px; }
}
.find__label { font-size: 0.75rem; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-muted); white-space: nowrap; }
.find__input {
  flex: 1 1 auto; min-width: 0;
  font: inherit; font-size: 1rem;  /* 16px floor: iOS Safari auto-zooms below it and never zooms back */
  min-height: 44px;
  border: 0; background: transparent; color: var(--ink);
  appearance: none; -webkit-appearance: none;
}
.find__input:focus { outline: none; }  /* the wrapper carries it, see above */
/* opacity:1 is load-bearing: Firefox otherwise composites the placeholder at
   0.54 and drops it to about 2.8:1. */
.find__input::placeholder { color: var(--ink-muted); opacity: 1; }
.find__btn {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px; padding: 0;
  background: transparent; border: 0; color: var(--ink); cursor: pointer;
}
.find__btn:hover { color: var(--brick); }
.find__icon { display: block; width: 18px; height: 18px; }
@media (forced-colors: active) {
  .find__btn { color: ButtonText; }
  .find__btn:hover { color: Highlight; }
}
/* Brick underline, as drawn, rather than the accent-coloured text used
   elsewhere: this sits beside the search field, and two brick links a centimetre
   apart would read as one control. */
.masthead__all {
  display: inline-flex; align-items: center; flex-shrink: 0; min-height: 44px;
  padding-bottom: 6px;
  font-size: 0.75rem; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--ink); text-decoration: none; white-space: nowrap;
  border-bottom: 1.5px solid var(--brick);
}
.masthead__all:hover, .masthead__all:focus-visible { color: var(--brick); }
/* Below ~1120px the one-line row crushes the field (the input has been
   measured at 60px). Tools take the next row so Find keeps a real width. */
@media (max-width: 70rem) {
  /* shrink: 0 is load-bearing: basis 100% alone still lets the group
     squeeze onto the wordmark's row and crush the input to ~60px. */
  .masthead__tools { flex: 1 0 100%; margin-inline-start: 0; }
}
@media (max-width: 48rem) {
  .masthead__tools { margin-top: 0.5rem; }
  .find { flex: 1 1 auto; min-width: 0; }
  /* All brands is already the first entry in the drawer's register.
     Shelf is not in the drawer: hide only the register link. */
  .masthead__all:not(.masthead__shelf) { display: none; }
}

/* --- nav: eight ruled cells ----------------------------------------------- */

.nav { margin-top: 1.375rem; }
.nav__rules { border-top: 1.5px solid var(--ink); border-bottom: 1px solid var(--ink); padding-block: 1.5px; }
.nav__row { display: flex; border-top: 1px solid var(--ink); }
.nav__cell {
  flex: 1; min-width: 0;
  /* inline-flex, not block: min-height does nothing to an inline anchor, and
     at 12px/1.55 these cells land at ~19px without it. */
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; padding: 0.375rem 0.25rem; position: relative;
  font-size: 0.75rem; font-weight: 600; letter-spacing: 0.13em; text-transform: uppercase;
  text-align: center; color: var(--ink); text-decoration: none;
  border-right: 1px solid var(--rule);
}
.nav__cell:last-child { border-right: 0; }
.nav__cell--all { color: var(--brick); }
.nav__cell:hover, .nav__cell:focus-visible { background: var(--hover-wash); }
/* aria-current is NOT signalled by the wash alone: hover-wash against paper is
   1.11:1, so "which one is current" would be invisible in greyscale and would
   also be indistinguishable from whatever the pointer happens to be over. The
   rule is drawn on every cell and scaled to 0, so marking one shifts nothing. */
.nav__cell::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: var(--ink); transform: scaleX(0); transform-origin: left;
}
.nav__cell[aria-current="page"] { background: var(--hover-wash); }
.nav__cell[aria-current="page"]::after { transform: scaleX(1); }

/* --- drawer: the mobile disclosure ---------------------------------------- */

/* The two navigations swap with display:none and nothing else. A clip,
   transform, opacity or max-height:0 leaves the hidden one focusable, which on
   desktop would mean 36 invisible tab stops with no visible ring. */
.drawer__nav { display: none; }
@media (max-width: 48rem) {
  .nav { display: none; }
  .drawer__nav { display: block; margin-top: 1rem; }
}

.drawer { border-top: 1.5px solid var(--ink); border-bottom: 1.5px solid var(--ink); }
/* No `display` on the <summary> itself - WebKit has a history of degrading
   summary activation when it is overridden. The flex lives on a child. */
.drawer__toggle { list-style: none; cursor: pointer; }
.drawer__toggle::-webkit-details-marker { display: none; }
.drawer__toggle-inner {
  display: flex; align-items: center; gap: 0.75rem;
  min-height: 44px; padding-inline: 1.25rem;
  font-size: 0.6875rem; font-weight: 600; letter-spacing: 0.13em; text-transform: uppercase;
}
.drawer__toggle:hover .drawer__toggle-inner { background: var(--hover-wash); }
/* Borders, not background-color: forced-colors flattens backgrounds to Canvas
   and would erase the bars entirely. Border colours survive. */
.drawer__bars {
  width: 24px; height: 13px;
  border-top: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  position: relative;
}
.drawer__bars::before {
  content: ""; position: absolute; left: 0; right: 0; top: 50%;
  border-top: 1.5px solid currentColor; transform: translateY(-50%);
}
/* Open/closed is not carried by the glyph alone - <summary> announces the
   state - but sighted users get it too: the bars close up to an X. */
.drawer[open] .drawer__bars { border-color: transparent; height: 16px; }
.drawer[open] .drawer__bars::before { top: 50%; transform: translateY(-50%) rotate(45deg); }
.drawer[open] .drawer__bars::after {
  content: ""; position: absolute; left: 0; right: 0; top: 50%;
  border-top: 1.5px solid currentColor; transform: translateY(-50%) rotate(-45deg);
}

/* In flow, never overlaid: the panel pushes the page down. An absolutely
   positioned panel would sit on top of the first link in <main> the moment
   focus tabbed past the last drawer link, which is what 2.4.11 prohibits. */
.drawer__panel { background: var(--paper-panel); border-top: 1px solid var(--ink); padding-block: 1rem 1.25rem; }
.drawer__heading {
  margin: 1rem 0 0.25rem; font-size: 0.6875rem; font-weight: 700;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink-muted);
}
.drawer__heading:first-child { margin-top: 0; }
.drawer__list { list-style: none; margin: 0; padding: 0; columns: 2; column-gap: 1.5rem; }
.drawer__list--wide { columns: 1; }
.drawer__list li { break-inside: avoid; }
.drawer__list a {
  display: flex; align-items: center; min-height: 44px;
  color: var(--ink-body); text-decoration: none;
  border-bottom: 1px solid var(--rule);
  font-size: 0.9375rem;
}
.drawer__list a:hover, .drawer__list a:focus-visible { color: var(--brick); text-decoration: underline; }
.drawer__list a[aria-current="page"] { color: var(--ink); font-weight: 700; }
.drawer__skip {
  display: inline-flex; align-items: center; min-height: 44px; margin-top: 0.75rem;
  color: var(--brick); text-decoration: underline; text-underline-offset: 0.15em;
}

/* --- links ---------------------------------------------------------------- */

/* Inline prose links. .card__brand, .card__link and .dcard__title are
   deliberately NOT in here: they sit inside stretched-link cards and carry
   their own treatment. Guide index titles are heading-shaped (underline on
   hover only); the feed link and any intro links stay underlined at rest. */
.evidence a, .page__intro a, .crumb a, .bcard__title a, .pdp__brand a, .state a,
.guide p:not(.card__seller):not(.card__price):not(.card__promoted):not(.rating) a,
.guide li:not(.card) a, .guide td a, .guide__compare-note a, .guide__compare-empty a,
.guide-index .page__intro a, .guide-index__feed a {
  color: var(--brick);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.15em;
}
:is(.evidence a, .page__intro a, .crumb a, .bcard__title a, .pdp__brand a, .state a,
   .guide p:not(.card__seller):not(.card__price):not(.card__promoted):not(.rating) a,
   .guide li:not(.card) a, .guide td a, .guide__compare-note a, .guide__compare-empty a,
   .guide-index .page__intro a, .guide-index__feed a):hover,
:is(.evidence a, .page__intro a, .crumb a, .bcard__title a, .pdp__brand a, .state a,
   .guide p:not(.card__seller):not(.card__price):not(.card__promoted):not(.rating) a,
   .guide li:not(.card) a, .guide td a, .guide__compare-note a, .guide__compare-empty a,
   .guide-index .page__intro a, .guide-index__feed a):focus-visible {
  color: var(--rust-dark);
  text-decoration-thickness: 2px;
}

/* --- shared editorial bits ------------------------------------------------ */

.eyebrow {
  margin: 0 0 0.75rem; font-size: 0.6875rem; font-weight: 700;
  letter-spacing: 0.2em; text-transform: uppercase; color: var(--brick);
}

/* Section head: heading left, meta right, closed by the ink rule. */
.head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 0.75rem 2.5rem; flex-wrap: wrap;
  border-bottom: 1.5px solid var(--ink); padding-bottom: 0.625rem; margin-bottom: 1.625rem;
}
.head__title { margin: 0; font-family: var(--font-slab); font-size: 1.625rem; font-weight: 400; letter-spacing: 0.01em; }
.head__meta {
  margin: 0; font-size: 0.75rem; font-weight: 600;
  letter-spacing: 0.11em; text-transform: uppercase; color: var(--ink-muted);
}
.head__link { color: var(--brick); text-decoration: none; }
.head__link:hover, .head__link:focus-visible { text-decoration: underline; text-underline-offset: 0.15em; }
/* Drawn, with an empty alt string: an arrow is a symbol with a spoken name,
   unlike punctuation. See .claude/accessibility.md on separators. */
.head__link::after { content: " \2192" / ""; }
@media (max-width: 48rem) { .head__title { font-size: 1.25rem; } }

/* Full bleed from inside the centred <main>. html/body carry overflow-x:clip,
   which is what keeps the 50vw arithmetic from producing a scrollbar. */
.band {
  margin-inline: calc(50% - 50vw); padding-inline: calc(50vw - 50%);
  border-block: 1.5px solid var(--ink);
}
.band--panel { background: var(--paper-panel); }
.band--dark {
  background: var(--ink); color: var(--cream); border-color: var(--ink);
  /* Every ring inside this band lands on ink, where the ink ring is 1.00:1. */
  --focus: var(--focus-on-dark);
}
.band--dark .head { border-bottom-color: var(--dark-rule); }
.band--dark .head__title { color: var(--cream-strong); }
.band--dark .head__meta { color: var(--cream-muted); }
.band .wrap { padding-inline: 0; }

/* Photography that does not exist yet. Shipped as the empty state, so the
   caption reads as a statement rather than as a note to a photographer - the
   crop ratio lives here, in aspect-ratio, not in the caption text. */
.ph {
  display: flex; align-items: flex-end; padding: 0.875rem;
  border: 1px solid var(--rule); aspect-ratio: 3 / 2;
  background: repeating-linear-gradient(135deg, var(--placeholder-a) 0 12px, var(--placeholder-b) 12px 24px);
}
.ph--tall { aspect-ratio: 2 / 3; }
.ph--wide { aspect-ratio: 7 / 5; }
.ph--thumb { aspect-ratio: 1; padding: 0; }
.ph--thumb .ph__cap { display: none; }
/* A slot that has its photograph. Reuses the .ph layout plumbing; the stripes
   sit invisibly behind the cover image. NO mix-blend-mode here - that rule is
   for cut-out product shots on paper plates, and multiplying a photograph
   muddies it (review condition, 2026-08-11). */
.ph--photo { padding: 0; border: 1px solid var(--rule); }
.ph--photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* The panel dividers are the 2px gap showing the --rule background through;
   a border here would double the line. */
.makers__panels .ph--photo { border: 0; }
.ph__cap {
  font-family: var(--font-mono); font-size: 0.65625rem; letter-spacing: 0.05em;
  /* --ink-muted, not the handoff's #8A7C62: that is 3.4:1 on the caption's own
     paper chip, and this is real text. */
  color: var(--ink-muted); background: var(--paper); border: 1px solid var(--rule);
  padding: 0.375rem 0.5625rem;
}

/* --- hero ----------------------------------------------------------------- */

.hero {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 40rem);
  gap: 3.5rem; align-items: center; padding-block: 3rem 2.5rem;
}
.hero__text { min-width: 0; }
.hero__headline {
  margin: 0; font-family: var(--font-serif); font-weight: 400;
  font-size: clamp(2.75rem, 5.5vw, 5rem); line-height: 0.98; letter-spacing: -0.022em;
  max-width: 13ch; text-wrap: pretty; overflow-wrap: break-word;
}
.hero__lead {
  margin: 1.625rem 0 0; max-width: 52ch;
  font-family: var(--font-serif); font-size: 1.25rem; line-height: 1.52; color: var(--ink-body);
}
.pills { display: flex; gap: 0.625rem; flex-wrap: wrap; list-style: none; margin: 1.875rem 0 0; padding: 0; }
.pill {
  display: inline-flex; align-items: center; min-height: 44px; padding-inline: 1.125rem;
  border: 1px solid var(--ink); border-radius: 999px;
  font-size: 0.75rem; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink); text-decoration: none;
}
.pill:hover, .pill:focus-visible { background: var(--ink); color: var(--paper); }

/* The explicit height is load-bearing: the rows are 1fr, and without it they
   collapse to content height, which for an image at height:100% is zero. */
.mosaic {
  display: grid; grid-template-columns: 1.35fr 1fr 1fr; grid-template-rows: 1fr 1fr;
  gap: 0.625rem; min-width: 0; height: 452px;
}
.mosaic > .ph--tall { grid-row: span 2; aspect-ratio: auto; }
.mosaic > .ph { aspect-ratio: auto; }
.mosaic__tile { border: 1px solid var(--rule); background: var(--paper-panel); overflow: hidden; }
/* multiply so cut-out product shots sit on the paper rather than on a white box */
.mosaic__tile img { width: 100%; height: 100%; object-fit: cover; mix-blend-mode: multiply; }
@media (max-width: 62rem) {
  .hero { grid-template-columns: minmax(0, 1fr); gap: 2rem; padding-block: 1.75rem 1.5rem; }
  .mosaic { grid-template-columns: 1.25fr 1fr; height: 300px; }
  .mosaic > .ph:last-child { display: none; }
  .mosaic__tile:nth-of-type(3) { display: none; }
}

/* --- stats ---------------------------------------------------------------- */

.stats { border-block: 1.5px solid var(--ink); }
.stats__list { display: grid; grid-template-columns: repeat(3, 1fr); list-style: none; margin: 0; padding: 0; }
.stats__list li { display: flex; align-items: baseline; gap: 1rem; padding-block: 1.375rem; min-width: 0; }
.stats__list li + li { border-inline-start: 1px solid var(--rule); padding-inline-start: 2.5rem; }
.stats__n { font-family: var(--font-slab); font-size: 3.25rem; line-height: 0.82; font-variant-numeric: tabular-nums; }
.stats__label {
  font-size: 0.75rem; font-weight: 600; letter-spacing: 0.11em;
  text-transform: uppercase; color: var(--ink-muted); line-height: 1.4;
}
@media (max-width: 48rem) {
  /* Three stacked ledger rows: number left, label right-aligned. */
  .stats__list { grid-template-columns: minmax(0, 1fr); }
  .stats__list li { justify-content: space-between; padding-block: 0.875rem; }
  .stats__list li + li { border-inline-start: 0; padding-inline-start: 0; border-top: 1px solid var(--rule-soft); }
  .stats__n { font-size: 2.375rem; }
  .stats__label { text-align: end; }
}

.chips {
  display: flex; flex-wrap: wrap; gap: 0.25rem 0.5rem;
  list-style: none; margin: 0; padding: 0;
}
.chip {
  display: inline-flex; align-items: center; min-height: 44px; padding-inline: 0.85rem;
  background: var(--sand); border: 1px solid var(--line);
  border-radius: 999px; color: var(--ink); text-decoration: none;
}
.chip:hover, .chip:focus-visible { background: var(--rust); border-color: var(--rust); color: var(--on-accent); }

/* --- sections ------------------------------------------------------------- */

.section { padding-block: 3.25rem 3.5rem; }
.section__heading { font-family: var(--font-slab); font-weight: 400; font-size: 1.625rem; margin: 0 0 1rem; }
.section__more { text-align: center; margin: 1.75rem 0 0; }
@media (max-width: 48rem) { .section { padding-block: 1.75rem 1.875rem; } }

/* --- department cards ----------------------------------------------------- */

.dgrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1.625rem; list-style: none; margin: 0; padding: 0; }
/* Same stretched-link contract as .card: the anchor wraps the department name
   only, and a ::after overlay carries the hit area, so the accessible name
   stays "Pantry" and not "Pantry 32 brands Jams, oils, salsa and staples." */
.dcard { position: relative; isolation: isolate; min-width: 0; }
.dcard__row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 0.875rem;
  padding-top: 0.875rem; padding-bottom: 0.625rem; border-bottom: 1px solid var(--rule-soft);
}
.dcard__title { margin: 0; font-family: var(--font-serif); font-weight: 400; font-size: 1.6875rem; letter-spacing: -0.015em; }
.dcard__link { color: var(--ink); text-decoration: none; }
.dcard__link::after { content: ""; position: absolute; inset: 0; z-index: 0; }
/* Hover moves the title to brick. Colour alone is not the cue - it picks up an
   underline at the same time, per the house link rule. */
.dcard:hover .dcard__link, .dcard__link:focus-visible {
  color: var(--brick); text-decoration: underline; text-underline-offset: 0.12em;
}
.dcard__count {
  font-size: 0.6875rem; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--brick); font-variant-numeric: tabular-nums; white-space: nowrap;
}
.dcard__blurb { display: block; padding-top: 0.625rem; font-size: 0.875rem; color: var(--ink-muted); }
@supports selector(:has(*)) {
  .dcard:has(.dcard__link:focus-visible) { outline: 3px solid var(--focus); outline-offset: 4px; }
  .dcard:has(.dcard__link:focus-visible) .dcard__link:focus-visible { outline: none; }
}
@media (max-width: 62rem) { .dgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; } }

.also { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; margin: 1.75rem 0 0; }
.also__label {
  font-size: 0.75rem; font-weight: 600; letter-spacing: 0.11em;
  text-transform: uppercase; color: var(--ink-muted);
}
.tagchip {
  display: inline-flex; align-items: center; min-height: 44px; padding-inline: 0.8125rem;
  border: 1px solid var(--rule-chip); background: var(--paper-chip);
  font-size: 0.75rem; font-weight: 500; color: var(--ink-body); text-decoration: none;
}
.tagchip:hover, .tagchip:focus-visible { border-color: var(--ink); color: var(--ink); text-decoration: underline; text-underline-offset: 0.15em; }

/* --- makers band ---------------------------------------------------------- */

.makers { padding-block: 0; }  /* padding-block, NOT padding: the shorthand wipes .band's full-bleed gutter */
.makers__head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 1.5rem 2.5rem; flex-wrap: wrap; padding-block: 2.125rem 1.625rem;
}
.makers__title {
  margin: 0; font-family: var(--font-serif); font-weight: 400;
  font-size: clamp(1.75rem, 3.5vw, 2.75rem); line-height: 1.05; letter-spacing: -0.02em; max-width: 20ch;
}
.makers__copy {
  margin: 0; max-width: 44ch; font-family: var(--font-serif);
  font-size: 1.125rem; line-height: 1.55; color: var(--ink-body);
}
.makers__copy a { color: var(--brick); text-decoration: underline; text-underline-offset: 0.15em; }
/* 2px gap with the rule colour behind, so the divider is the background
   showing through rather than three borders that double up. */
.makers__panels {
  display: grid; grid-template-columns: 1.4fr 1fr 1fr; gap: 2px;
  background: var(--rule); border-top: 1px solid var(--rule);
}
.makers__panels .ph { border: 0; aspect-ratio: auto; min-height: 400px; }
@media (max-width: 48rem) {
  /* Stacked, NOT a horizontal scroller as drawn. The panels contain nothing
     focusable, so as a scroller the second and third are unreachable on iOS
     Safari, and the fixes for that (tabindex on the container) put a tab stop
     on desktop where there is nothing to scroll. Stacking costs nothing. */
  .makers__panels { grid-template-columns: minmax(0, 1fr); }
  .makers__panels .ph { min-height: 220px; }
}

/* --- the index ------------------------------------------------------------ */

.az {
  display: flex; flex-wrap: wrap; margin: 0;
  padding-block: 0.5rem 0.75rem; border-bottom: 1px solid var(--rule);
}
/* 2.5.8: the pitch has to be the box, so the spacing comes from padding rather
   than gap. That leaves the glyphs exactly where they are drawn while the
   targets touch at 28x44. */
.az__l {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 28px; min-height: 44px;
  font-size: 0.8125rem; font-weight: 600; letter-spacing: 0.08em;
  color: var(--ink-muted); text-decoration: none;
}
a.az__l:hover, a.az__l:focus-visible { color: var(--brick); text-decoration: underline; text-underline-offset: 0.15em; }
/* A letter with nothing behind it is not a link to a missing anchor. */
.az__l--none { color: var(--disabled); }

.irows { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 3rem; list-style: none; margin: 0; padding: 0; }
.irow {
  position: relative; isolation: isolate;
  display: grid; grid-template-columns: 56px minmax(0, 1fr) auto; gap: 1rem; align-items: center;
  padding: 0.6875rem 0.25rem; border-bottom: 1px solid var(--rule-soft); min-width: 0;
}
.irow .ph, .irow__thumb { width: 56px; height: 56px; }
.irow__thumb { display: block; border: 1px solid var(--rule); background: var(--paper-panel); overflow: hidden; }
.irow__thumb img { width: 100%; height: 100%; object-fit: cover; mix-blend-mode: multiply; }
.irow__name { min-width: 0; }
.irow__link {
  display: block; font-family: var(--font-serif); font-size: 1.3125rem;
  letter-spacing: -0.01em; line-height: 1.2; color: var(--ink); text-decoration: none;
  overflow-wrap: break-word;
}
.irow__link::after { content: ""; position: absolute; inset: 0; z-index: 0; }
.irow__dept { display: block; font-size: 0.75rem; color: var(--ink-faint); letter-spacing: 0.02em; margin-top: 2px; }
.irow__city {
  font-size: 0.75rem; font-weight: 600; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--brick); white-space: nowrap;
}
.irow:hover { background: var(--hover-wash); }
.irow:hover .irow__link, .irow__link:focus-visible { text-decoration: underline; text-underline-offset: 0.12em; }
@supports selector(:has(*)) {
  .irow:has(.irow__link:focus-visible) { outline: 3px solid var(--focus); outline-offset: -1px; }
  .irow:has(.irow__link:focus-visible) .irow__link:focus-visible { outline: none; }
}
@media (max-width: 48rem) {
  .irows { grid-template-columns: minmax(0, 1fr); }
  .irow { grid-template-columns: 48px minmax(0, 1fr) auto; min-height: 66px; }
  .irow .ph, .irow__thumb { width: 48px; height: 48px; }
}

/* --- the shelf ------------------------------------------------------------ */

/* Card save toggle. Sits ABOVE the stretched-link overlay (same layer rule as
   .card__seller) so the card's ::after cannot swallow its clicks, positioned
   over the media corner so it costs the card no layout height. Hidden until
   shelf.js runs: no JS, no dead buttons. */
.shelfbtn {
  position: absolute; top: 8px; inset-inline-end: 8px; z-index: 2;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px; padding: 0;
  background: var(--paper); border: 1.5px solid var(--rule-chip); border-radius: 999px;
  cursor: pointer;
}
/* The ring is drawn INSET (the .skip precedent): the button floats over a
   photograph, and no focus token survives arbitrary image pixels. The opaque
   paper plate is what the inset ring lands on. */
.shelfbtn:focus-visible { outline: 3px solid var(--focus); outline-offset: -3px; }
.shelfbtn:hover { border-color: var(--ink); }
/* The glyph is a drawn bookmark ribbon: two clipped layers, not a glyph
   character, so nothing is exposed to the accessibility tree. The first
   attempt drew it with borders and border-bottom: 0, which renders as an
   upside-down U - a clip-path cuts the notch, but there is no border along a
   clipped edge to see. The fill-plus-inset-fill construction draws the
   outline on every edge, notch included. */
.shelfbtn__glyph {
  width: 12px; height: 16px; position: relative;
  background: var(--ink-muted);
  clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 68%, 0 100%);
}
.shelfbtn__glyph::before {
  content: ""; position: absolute; inset: 2px 2px 4px;
  background: var(--paper);
  clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 62%, 0 100%);
}
.shelfbtn:hover .shelfbtn__glyph { background: var(--ink); }
.shelfbtn.is-saved { border-color: var(--brick); }
/* Saved = solid ribbon: the inner paper layer goes, the fill turns brick. */
.shelfbtn.is-saved .shelfbtn__glyph { background: var(--brick); }
.shelfbtn.is-saved .shelfbtn__glyph::before { display: none; }
@media (forced-colors: active) {
  /* Backgrounds are flattened under forced colors, so the glyph declares
     system colors explicitly and opts out of adjustment. */
  .shelfbtn { border-color: ButtonBorder; }
  .shelfbtn__glyph { forced-color-adjust: none; background: ButtonText; }
  .shelfbtn__glyph::before { background: ButtonFace; }
  .shelfbtn.is-saved { forced-color-adjust: none; background: Highlight; }
  .shelfbtn.is-saved .shelfbtn__glyph { background: HighlightText; }
}

.pdp__save { margin-inline-start: 0.75rem; }
@media (max-width: 30rem) { .pdp__save { margin-inline-start: 0; margin-top: 0.625rem; } }

/* Header link + count. The count is REAL TEXT inside the link's name
   ("Shelf 3 saved"), never aria-label and never live. Hidden at zero. */
.masthead__shelf { border-bottom-color: var(--ink); }
.shelfcount {
  margin-inline-start: 0.1rem; font-variant-numeric: tabular-nums;
  color: var(--brick);
}

/* --- star ratings --------------------------------------------------------- */

/* Two overlaid rows of glyphs: the grey row underneath, the brick row clipped
   to the score on top. The whole visual is aria-hidden and announced once as a
   sentence from the visually-hidden span - see stars() in partials.js. */
.rating { display: flex; align-items: center; gap: 0.375rem; margin: 0.5rem 0 0; }
.stars {
  position: relative; display: inline-block; flex: none;
  font-size: 0.8125rem; line-height: 1; letter-spacing: 0.06em;
  color: var(--rule-chip); white-space: nowrap;
}
.stars__on {
  position: absolute; inset-block-start: 0; inset-inline-start: 0;
  overflow: hidden; color: var(--brick); white-space: nowrap;
}
.rating__val { font-size: 0.75rem; font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
.rating__n { font-size: 0.75rem; color: var(--ink-muted); font-variant-numeric: tabular-nums; }
/* Under forced colours both star rows collapse to the same colour, so the fill
   stops meaning anything. The number is always there in text, which is why it
   is rendered as well as the stars rather than instead of them. */
@media (forced-colors: active) { .stars { display: none; } }

/* min-height matches the stars row, so the empty spacer on unrated cards
   holds the same vertical slot as a real rating. */
.card .rating { margin: 0.375rem 0 0; min-height: 0.8125rem; }
.pdp__rating { margin: 0.875rem 0 0; }
.pdp__rating .stars { font-size: 1.125rem; }
.pdp__rating .rating__val, .pdp__rating .rating__n { font-size: 0.875rem; }

/* --- where they are ------------------------------------------------------- */

.where { display: grid; grid-template-columns: minmax(0, 1fr) 25rem; gap: 4rem; align-items: center; }
.map { width: 100%; height: auto; display: block; }
.map__dot { fill: var(--ink-faint); }
.map__halo { fill: var(--brick); opacity: 0.32; }
.map__major { fill: var(--cream-strong); }
.map__label { font-family: var(--font-ui); font-size: 27px; font-weight: 600; letter-spacing: 1.4px; fill: var(--cream-strong); }
.map__label--minor { font-size: 23px; font-weight: 500; letter-spacing: 1.2px; fill: var(--cream-muted); }
.where__lead { margin: 0 0 1.5rem; font-family: var(--font-serif); font-size: 1.3125rem; line-height: 1.5; color: var(--cream-muted); }
.crows { list-style: none; margin: 0; padding: 0; }
.crow {
  position: relative; isolation: isolate;
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: baseline;
  padding: 0.9375rem 0.125rem; border-top: 1px solid var(--dark-rule);
}
.crow:first-child { border-top-color: var(--dark-rule-strong); }
.crow:last-child { border-bottom: 1px solid var(--dark-rule); }
.crow__link { font-family: var(--font-serif); font-size: 1.6875rem; color: var(--cream-strong); text-decoration: none; }
.crow__link::after { content: ""; position: absolute; inset: 0; z-index: 0; }
.crow:hover .crow__link, .crow__link:focus-visible { color: var(--brick-light); text-decoration: underline; text-underline-offset: 0.12em; }
.crow__n { font-family: var(--font-slab); font-size: 1.5625rem; font-variant-numeric: tabular-nums; }
.where__note { margin: 1.125rem 0 0; font-size: 0.84375rem; line-height: 1.6; color: var(--cream-muted); }
.where__note a { color: var(--brick-light); text-decoration: underline; text-underline-offset: 0.15em; }
@supports selector(:has(*)) {
  .crow:has(.crow__link:focus-visible) { outline: 3px solid var(--focus-on-dark); outline-offset: -1px; }
  .crow:has(.crow__link:focus-visible) .crow__link:focus-visible { outline: none; }
}
@media (max-width: 62rem) {
  .where { grid-template-columns: minmax(0, 1fr); gap: 2rem; }
}

/* --- rules band ----------------------------------------------------------- */

.rules { border-bottom: 0; }
.rules__grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 2.75rem; padding-block: 2.5rem; }
.rules__text { margin: 0; font-family: var(--font-serif); font-size: 1.3125rem; line-height: 1.42; color: var(--ink-body); }
@media (max-width: 48rem) {
  .rules__grid { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .rules__grid > div { padding-block: 1.125rem; }
  .rules__grid > div + div { border-top: 1px solid var(--rule-soft); }
}

/* --- brand index ---------------------------------------------------------- */

.brows { list-style: none; margin: 0; padding: 0; }
.brow {
  position: relative; isolation: isolate;
  /* FIXED widths on columns 2 and 3, not auto: every row is its own grid, so
     an auto city column made "WACO" and "DRIPPING SPRINGS" rows put the
     department column's left edge at different x positions - the ragged
     mid-air text the owner flagged. Fixed tracks give both columns one
     straight edge down the page, like the table this is pretending to be.
     11rem clears "DRIPPING SPRINGS" at 12px/0.1em with room to spare. */
  display: grid; grid-template-columns: minmax(0, 1fr) 16rem 11rem;
  gap: 0.25rem 1.5rem; align-items: baseline;
  padding: 0.625rem 0.25rem; border-bottom: 1px solid var(--rule-soft); min-width: 0;
}
.brow__name {
  margin: 0; font-family: var(--font-serif); font-weight: 400; font-size: 1.25rem;
  letter-spacing: -0.01em; line-height: 1.25; overflow-wrap: break-word; min-width: 0;
}
.brow__link { color: var(--ink); text-decoration: none; }
.brow__link::after { content: ""; position: absolute; inset: 0; z-index: 0; }
.brow__dept { font-size: 0.8125rem; color: var(--ink-faint); min-width: 0; }
/* Right-aligned into a fixed track, so the ragged edge falls on the left,
   inside the row, and the page's right margin stays a hard line. */
.brow__city {
  font-size: 0.75rem; font-weight: 600; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--brick); white-space: nowrap;
  text-align: end;
}
/* The Shop tag sits above the stretched overlay so it stays selectable. */
.brow .tag { position: relative; z-index: 1; }
.brow:hover { background: var(--hover-wash); }
.brow:hover .brow__link, .brow__link:focus-visible { text-decoration: underline; text-underline-offset: 0.12em; }
@supports selector(:has(*)) {
  .brow:has(.brow__link:focus-visible) { outline: 3px solid var(--focus); outline-offset: -1px; }
  .brow:has(.brow__link:focus-visible) .brow__link:focus-visible { outline: none; }
}
@media (max-width: 48rem) {
  .brow { grid-template-columns: minmax(0, 1fr) auto; }
  .brow__dept { grid-column: 1; }
  .brow__city { grid-row: 1; grid-column: 2; }
}

/* --- brand index letters -------------------------------------------------- */

.section--letter { padding-block: 1.5rem 0.5rem; }
.letter {
  margin: 0 0 1rem; font-family: var(--font-slab); font-weight: 400; font-size: 1.375rem;
  border-bottom: 1.5px solid var(--ink); padding-bottom: 0.5rem;
  scroll-margin-top: 1rem;
}
.letter:focus { outline: none; }
.page__title { font-size: clamp(1.7rem, 4vw, 2.25rem); margin: 1.5rem 0 0.5rem; letter-spacing: -0.02em; overflow-wrap: break-word; }
.page__intro { color: var(--ink-soft); margin: 0 0 1rem; max-width: 46rem; }
.crumb { margin: 1.25rem 0 0; font-size: 0.9rem; }
.crumb a { display: inline-flex; align-items: center; min-height: 44px; }

/* --- grids ---------------------------------------------------------------- */

.grid { display: grid; gap: 1.375rem; grid-template-columns: repeat(6, minmax(0, 1fr)); list-style: none; margin: 0; padding: 0; }
.bgrid { display: grid; gap: var(--gap); grid-template-columns: repeat(auto-fill, minmax(min(17rem, 100%), 1fr)); list-style: none; margin: 0; padding: 0; }
.card, .bcard, .dcard { min-width: 0; }
@media (max-width: 75rem) { .grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (max-width: 48rem) { .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; } }

/* Stretched link. .card must stay position:relative + isolation:isolate, and
   must NEVER get overflow:hidden, transform, filter or a fixed height: each
   one either clips the focus ring or re-parents the overlay above the nested
   brand link and steals its clicks. Clip the image instead. */
/* The Ledger card carries no box of its own: the only border is the image
   plate. .card still MUST keep position:relative + isolation:isolate and must
   never get overflow:hidden, transform, filter or a fixed height. */
.card {
  position: relative; isolation: isolate; overflow: visible;
  display: flex; flex-direction: column;
}
.card__media { aspect-ratio: 1 / 1; background: var(--paper-panel); border: 1px solid var(--rule); overflow: hidden; }
/* multiply keeps cut-out product shots on the paper instead of on a white box */
.card__media img { width: 100%; height: 100%; object-fit: cover; mix-blend-mode: multiply; }
.card__media-empty { width: 100%; height: 100%; }
.card__body { display: flex; flex-direction: column; flex: 1; }
/* The two min-heights are what hold every price in the row on one baseline
   when titles and seller names wrap to different depths. Keep them. */
.card__title {
  margin: 0.75rem 0 0; min-height: 46px;
  font-family: var(--font-serif); font-weight: 400; font-size: 1.125rem; line-height: 1.25;
  overflow-wrap: break-word;
}
.card__link { color: var(--ink); text-decoration: none; }
.card__link::after { content: ""; position: absolute; inset: 0; z-index: 0; }
.card:hover .card__link, .card__link:focus-visible { color: var(--brick); text-decoration: underline; text-underline-offset: 0.12em; }
/* Raised above the overlay so they stay clickable and selectable. */
.card__seller, .card__price { position: relative; z-index: 1; }
.card__seller {
  margin: 0.375rem 0 0; min-height: 32px;
  font-size: 0.65625rem; font-weight: 600; letter-spacing: 0.11em; text-transform: uppercase;
  color: var(--ink-faint); display: flex; flex-wrap: wrap; gap: 0 0.4rem;
}
.card__seller .card__city::before { content: "\2014"; margin-inline-end: 0.4rem; }
/* The chosen options, on shelf rows only. The shelf saves offers, so two
   variants of one product can sit side by side under the same title - this
   line is what tells them apart, so it is real text, not decoration. Raised
   above the stretched-link overlay like the other body rows. Reuses
   --ink-muted, already contrast-checked against the card. */
.card__variant {
  position: relative; z-index: 1;
  margin: 0.25rem 0 0;
  font-size: 0.8125rem; color: var(--ink-muted);
}
/* 2.5.8: the stretched-link overlay is flush on all sides, so the spacing
   exception is unavailable and this needs real height. */
.card__brand { display: inline-flex; align-items: center; min-height: 24px; color: var(--ink-faint); }
.card__promoted { margin: 0.35rem 0 0; }
.card__price { margin: auto 0 0; padding-top: 0.5rem; font-family: var(--font-slab); font-size: 1rem; font-variant-numeric: tabular-nums; }
.card__price--none { font-family: var(--font-ui); font-size: 0.8125rem; color: var(--ink-muted); }
@media (max-width: 48rem) {
  .card__title { min-height: 44px; font-size: 1rem; }
  .card__seller { min-height: 30px; }
}
/* Ring the whole card, not the title text run, since the card is the target. */
@supports selector(:has(*)) {
  .card:has(.card__link:focus-visible) { outline: 3px solid var(--focus); outline-offset: 2px; }
  .card:has(.card__link:focus-visible) .card__link:focus-visible { outline: none; }
}

/* .dcard is NOT in here any more - it is a photo-led Ledger card now, styled
   in the department-cards block below. Only the brand card keeps the box. */
.bcard {
  border: 1px solid var(--rule); border-radius: var(--radius);
  padding: 1rem; background: var(--paper-panel);
}
.bcard__title { margin: 0 0 0.25rem; font-size: 1.05rem; overflow-wrap: break-word; }
.bcard__meta { margin: 0 0 0.5rem; color: var(--ink-muted); font-size: 0.9rem; display: flex; flex-wrap: wrap; gap: 0.4rem 1rem; }

/* --- brand page ----------------------------------------------------------- */

.evidence {
  background: var(--sand); border: 1px solid var(--line);
  border-left: 4px solid var(--rust);
  border-radius: var(--radius); padding: 0.85rem 1rem; margin: 0 0 1rem;
  font-size: 0.925rem; max-width: 46rem;
}
.evidence__label { font-weight: 700; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink-soft); }
.evidence__value { margin: 0.15rem 0 0; font-size: 1rem; }
.evidence__src { font-size: 0.85rem; }
.brand__note { color: var(--ink-soft); font-size: 0.925rem; max-width: 46rem; }
.brand__site { margin: 1rem 0 0; }

.btn {
  display: inline-flex; align-items: center; min-height: 44px;
  background: var(--rust); color: var(--on-accent);
  padding: 0.5rem 1.1rem; border: 1px solid var(--rust); border-radius: var(--radius);
  text-decoration: none; font-weight: 600;
}
.btn:hover, .btn:focus-visible { background: var(--rust-dark); border-color: var(--rust-dark); color: var(--on-accent); }

/* Must sit AFTER .btn: same specificity, so source order decides. */
.btn--ink {
  background: var(--ink); border-color: var(--ink); color: var(--paper);
  padding: 0.75rem 1.625rem; font-size: 0.75rem; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase;
}
.btn--ink:hover, .btn--ink:focus-visible { background: var(--brick); border-color: var(--brick); color: var(--on-accent); }

/* --- states --------------------------------------------------------------- */

.state {
  border: 1px dashed var(--line); border-radius: var(--radius);
  padding: 1.5rem; background: var(--sand); color: var(--ink-soft);
}
.state--inline { padding: 1rem; }
.state__heading { margin: 0 0 0.4rem; font-size: 1.05rem; color: var(--ink); }
.state--error { border-style: solid; border-left: 4px solid var(--rust); }

/* --- footer --------------------------------------------------------------- */

.footer {
  margin-top: 3rem; padding-block: 2.375rem 2.875rem;
  border-top: 1.5px solid var(--ink); background: var(--paper);
  color: var(--ink-muted); font-size: 0.75rem; line-height: 1.6;
}
/* Wordmark on its own line heading the columns, rather than beside them: as a
   flex row opposite two short lists it left a wide empty band across the
   middle of the footer. */
.footer__top { border-bottom: 1px solid var(--rule); padding-bottom: 1.625rem; }
.footer__wordmark { font-size: 1.375rem; margin-bottom: 1.5rem; }
.footer__cols {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 1.5rem 2.5rem; align-items: start;
}
/* Departments is the long one, so it runs two columns of its own rather than
   forcing every group to be eight rows tall. */
.footer__col:first-child .footer__links { columns: 2; column-gap: 1.5rem; }
.footer__col:first-child .footer__links li { break-inside: avoid; }
.footer__heading {
  margin: 0 0 0.75rem; font-size: 0.6875rem; font-weight: 700;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink-muted);
}
.footer__links { list-style: none; margin: 0; padding: 0; }
/* min-height, never height: 2.5.8 wants a real 44px box and a fixed height
   would clip a wrapped city name instead of growing. */
.footer__links a {
  display: flex; align-items: center; min-height: 44px;
  font-size: 0.875rem; color: var(--ink);
  text-decoration: none;
}
.footer__links a:hover, .footer__links a:focus-visible { color: var(--brick); text-decoration: underline; text-underline-offset: 0.15em; }
@media (max-width: 48rem) {
  /* Ruled rows on mobile: the rule is what makes a 44px target legible as a
     target rather than as loose spacing. */
  .footer__cols { grid-template-columns: minmax(0, 1fr); gap: 1.25rem; }
  .footer__col:first-child .footer__links { columns: 2; }
  .footer__links a { border-bottom: 1px solid var(--rule-soft); }
}
.footer__legal { margin-top: 1.25rem; max-width: 88ch; }
.footer__legal p { margin: 0 0 0.5rem; }
.footer__legal a { color: var(--brick); text-decoration: underline; text-underline-offset: 0.15em; }
.footer__copy { color: var(--ink-faint); }
.footer__note { margin: 1.25rem 0 0; max-width: 88ch; }
.footer__note--warn { font-weight: 600; color: var(--ink); }

/* --- department index ----------------------------------------------------- */

.deptrows { list-style: none; margin: 0; padding: 0; }
.deptrow {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline; gap: 0 1.25rem;
  border-bottom: 1px solid var(--rule-soft);
}
.deptrow__link {
  grid-column: 1; min-height: 44px; display: flex; align-items: center;
  font-family: var(--font-serif); font-size: 1.3125rem; letter-spacing: -0.015em;
  color: var(--ink); text-decoration: none;
}
.deptrow__link:hover, .deptrow__link:focus-visible { color: var(--brick); text-decoration: underline; text-underline-offset: 0.15em; }
.deptrow__count {
  grid-column: 2; grid-row: 1; margin: 0;
  font-size: 0.6875rem; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--brick); font-variant-numeric: tabular-nums; white-space: nowrap;
}
.deptrow__blurb { grid-column: 1 / -1; margin: 0 0 0.75rem; font-size: 0.875rem; color: var(--ink-muted); }

/* --- motion + forced colors ----------------------------------------------- */

@media (prefers-reduced-motion: no-preference) {
  .skip { transition: transform 150ms ease; }
  .nav__cell::after { transition: transform 200ms ease; }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important; animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important; scroll-behavior: auto !important;
  }
}
@media (forced-colors: active) {
  :focus-visible { outline-color: Highlight; }
  .card, .bcard, .dcard, .state, .evidence { border-color: CanvasText; }
  .chip, .btn { border-color: ButtonBorder; }
  .chip:hover, .btn:hover, .chip:focus-visible, .btn:focus-visible {
    forced-color-adjust: none; background: Highlight; color: HighlightText; border-color: Highlight;
  }
  /* The hover-wash fill flattens to Canvas here, so the rule is the only thing
     left carrying "current". */
  .nav__cell[aria-current="page"]::after { background: Highlight; }
  .nav__cell, .nav__rules, .nav__row, .drawer, .drawer__panel { border-color: CanvasText; }
  .find { border-color: CanvasText; }
}


/* ===========================================================================
   Search form
   =========================================================================== */

.searchform { display: flex; flex-wrap: wrap; gap: 0.5rem; flex: 1 1 16rem; min-width: 0; }
.searchform__input {
  flex: 1 1 8rem;
  min-width: 0;        /* a flex item's default min-width:auto resolves to the
                          input's ~20ch intrinsic width and overflows */
  font-size: 1rem;     /* 16px floor: iOS Safari auto-zooms below it and never zooms back */
  min-height: 44px; padding: 0.4rem 0.7rem;
  /* The field sits on the same --paper as the page behind it, so this border
     is its ONLY boundary and must carry 3:1. It does, at 3.48:1. */
  border: 1px solid var(--line);
  background: var(--paper); color: var(--ink); border-radius: var(--radius);
  appearance: none; -webkit-appearance: none;
}
.searchform__input::placeholder { color: var(--ink-soft); opacity: 1; }
.searchform__btn { flex: 0 0 auto; }
.searchform--page { display: block; margin-block: 0 1.5rem; }
.searchform__label { display: block; font-weight: 600; margin-block-end: 0.35rem; color: var(--ink); }
.searchform__row { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: stretch; }
@media (max-width: 48rem) { .nav { margin-inline-start: 0; } }

/* ===========================================================================
   Variant picker
   =========================================================================== */

.opts { display: grid; gap: 1rem; margin-block: 1.25rem 1.5rem; }
.opt { min-inline-size: 0; scroll-margin-top: calc(var(--header-h) + 1rem); }
.opt:focus { outline: none; }
/* :target matches unconditionally after a fragment jump; :focus-visible does not. */
.opt:target { box-shadow: inset 4px 0 0 var(--rust); padding-inline-start: 0.75rem; }
.opt__legend {
  margin: 0 0 0.5rem; font-size: 0.6875rem; font-weight: 600;
  letter-spacing: 0.11em; text-transform: uppercase; color: var(--ink-muted);
}
.opt__list {
  display: flex; flex-wrap: wrap;
  gap: 0.75rem;   /* not 0.5rem: a 3px ring at 2px offset needs 5px clearance each side */
  list-style: none; margin: 0; padding: 0;
}
.opt__list > li { display: flex; min-width: 0; max-width: 100%; }

.opt__val {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; min-width: 44px;   /* min-, never fixed: 2.5.8 + 1.4.12 */
  padding: 0.4rem 1.125rem 0.4rem 2.125rem;  /* reserves the check slot on every chip */
  position: relative;
  background: transparent; color: var(--ink);
  border: 1.5px solid var(--rule-chip); border-radius: 999px;
  font-size: 0.8125rem; font-weight: 500;
  text-decoration: none; text-align: center;
  max-width: 100%; overflow-wrap: anywhere;
}
a.opt__val:hover, a.opt__val:focus-visible { border-color: var(--ink); background: var(--hover-wash); }

/* Out of stock BEFORE is-current, so a selected-but-unavailable chip keeps the
   strike. Never opacity (50% = 2.21:1, and it dims the focus ring too), never
   pointer-events:none, never cursor:not-allowed - the link is still active.
   NOTE the two states are different elements, and deliberately so: a value the
   seller simply has not stocked stays an <a>, because it goes to a real page
   state. Only an impossible combination (exists === false) is a <span>, and it
   is a span because it has no URL to point at, not because it is unavailable.
   line-through is not exposed to the accessibility tree, which is why both
   carry visually-hidden text as well. */
/* border-color stays --rule-chip. --rule-soft is a decorative row hairline at
   1.28:1, and this border is the ONLY thing marking the edge of the control -
   the same reason the base .opt__val border is gated at 3:1. The 2px
   strikethrough carries the state instead. */
.opt__val.is-out { color: var(--disabled); border-color: var(--rule-chip); text-decoration: line-through; text-decoration-thickness: 2px; }
span.opt__val { cursor: default; }

/* Explains why the page is showing a variant the URL did not ask for. Uses
   --ink (full contrast), not --ink-soft: it is the reason the price differs
   from what was clicked, so it must not read as fine print. */
.opts__note {
  margin: 0 0 1rem; color: var(--ink); font-size: 0.925rem; max-width: 46rem;
  border-inline-start: 4px solid var(--rust); padding-inline-start: 0.75rem;
}

/* Ink fill, per the comp. The inset double ring is what keeps the selected
   state legible as a SHAPE and not only as a colour. */
.opt__val.is-current {
  background: var(--ink); border-color: var(--ink); color: var(--cream-strong);
  box-shadow: inset 0 0 0 2px var(--paper), inset 0 0 0 4px var(--ink);
}
a.opt__val.is-current:hover { background: var(--ink); border-color: var(--ink); }
/* Check drawn from borders, not content:"✓": generated content IS exposed to
   the accessibility tree and would double up with aria-current. Borders also
   survive forced-colors, box-shadow does not. */
.opt__val.is-current::before {
  content: ""; position: absolute; inset-inline-start: 0.75rem;
  width: 0.45em; height: 0.75em;
  border: solid currentColor; border-width: 0 0.16em 0.16em 0;
  transform: rotate(45deg) translateY(-0.12em);
}

/* Hidden until focused. Uses the clip technique rather than an offset
   transform: .opts is an unpositioned grid, so an absolutely positioned child
   lands on top of the price instead of off-screen. */
.opts__skip {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%);
  white-space: nowrap;
}
.opts__skip:focus {
  position: static; width: auto; height: auto;
  margin: 0 0 0.5rem; padding: 0.5rem 1rem; clip: auto; clip-path: none;
  display: inline-flex; align-items: center; min-height: 44px;
  background: var(--ink); color: var(--paper); text-decoration: underline;
}

/* ===========================================================================
   Product detail
   =========================================================================== */

.pdp {
  display: grid;
  /* minmax(0,1fr), not 1fr: a bare 1fr track has min-width:auto and one
     unbreakable SKU string blows past the container. */
  grid-template-columns: minmax(0, 1fr);
  gap: 4rem; align-items: start; margin-block: 1.5rem 0;
}
/* Media stays FIRST in the DOM and first in the grid. If it ever needs to sit
   right, change the order in pages.js - never order/row-reverse/direction,
   which desync focus order from visual order (2.4.3).
   Highlights and Description are NOT in this grid: they are .pdp__band below,
   so the <h1> and the buy button are not pushed under a column of prose. */
/* 1fr 2fr: the image column was ~54% of the content width and read as more
   than half the browser. This puts it at 431 of 1360 (31.7%), which is the
   1/3 - 2/3 split the owner asked for. */
@media (min-width: 48rem) { .pdp { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); } }
.pdp__info { min-width: 0; }

/* Was one box; now a plate plus a strip. The clip and the aspect-ratio belong
   to the PLATE alone. .pdp__media now contains focusable thumbnails and a skip
   link, and overflow:hidden on an ancestor shears a positive-offset focus ring
   and would swallow the skip link whole. Worse, it would make this a scroll
   container, so focusing thumbnail 8 would scroll the hero out of view with no
   scrollbar and no way back. Same ruling as .card / .card__media. */
.pdp__media { display: grid; gap: 0.75rem; position: relative; min-width: 0; }
.pdp__media:focus { outline: none; }

.pdp__hero {
  aspect-ratio: 1 / 1;   /* container reserves the box, so wrong img width/height
                            attributes cannot shift layout */
  background: var(--paper-panel);
  border: 1px solid var(--rule);  /* a photo is not a UI component, so 1.4.11 does not attach */
  overflow: hidden;      /* safe HERE: nothing inside .pdp__hero is focusable */
}
/* Scoped to the hero, NOT .pdp__media: a descendant selector would multiply
   every thumbnail against the page and against its own state ring. */
.pdp__hero img { width: 100%; height: 100%; object-fit: cover; mix-blend-mode: multiply; }

.gal {
  display: grid;
  /* auto-fill, per .bgrid: holds ~62px at the 1440 canvas and ~61px in the
     208px column at the 48rem breakpoint, and can never fall below 56px. */
  grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
  gap: 0.75rem;   /* not 0.5rem: a 3px ring at 2px offset needs 5px clearance
                     each side. Same number, same reason, as .opt__list. */
  list-style: none; margin: 0; padding: 0; min-width: 0;
  /* No overflow-x: auto. This site has no horizontal scrollers - see the
     makers band - and wrapping removes the whole class of
     iOS-Safari-unreachable-panel problem. */
}
.gal__thumb {
  display: block; position: relative; aspect-ratio: 1 / 1;
  /* 2.5.8 passes on SIZE outright at ~60px, not on the spacing exception, and
     it clears the house 44px floor. Do not switch the stated basis. */
  min-width: 44px; min-height: 44px;
  background: var(--paper-panel);
  /* This is the sole edge of a CONTROL now, so 1.4.11 attaches where it did
     not on the plate. --rule-chip is 3.48:1. NEVER --rule or --rule-soft,
     which are decorative hairlines and ungated for that reason. */
  border: 1.5px solid var(--rule-chip);
  overflow: hidden;   /* clips the img only; an element's own outline is not
                         clipped by its own overflow, only by an ancestor's */
}
.gal__thumb img { width: 100%; height: 100%; object-fit: cover; }
.gal__thumb:hover, .gal__thumb:focus-visible { border-color: var(--ink); }

/* State is drawn from the BORDER plus an ::after overlay, NOT from an inset
   box-shadow the way .opt__val.is-current does it. That works on a chip only
   because the chip has padding and its content is text; a thumbnail anchor has
   no padding and its img is width/height 100%, so the child completely covers
   the padding box where an inset shadow paints and the ring is invisible.
   Borders also survive forced-colors, box-shadow does not. No content:"✓" -
   generated content IS exposed to the accessibility tree and would duplicate
   aria-current. */
.gal__thumb.is-current { border-color: var(--ink); border-width: 3px; }
.gal__thumb.is-current::after {
  content: ""; position: absolute; inset: 0;
  border: 2px solid var(--paper); pointer-events: none;
}
/* :target matches unconditionally after a fragment jump; :focus-visible does
   not. Compounding :focus makes the ring leave when focus does, which a bare
   :target rule would not - the fragment stays in the URL. */
.gal__thumb:target:focus { outline: 3px solid var(--focus); outline-offset: 2px; }

.pdp__title:focus { outline: none; }

.pdp__noimage {
  display: grid; place-items: center; aspect-ratio: 1 / 1; margin: 0; padding: 1rem;
  background: var(--paper-panel); border: 1px solid var(--rule);
  color: var(--ink-muted); text-align: center; font-size: 0.9rem;
}

.pdp__dept { color: inherit; text-decoration: none; }
.pdp__dept:hover, .pdp__dept:focus-visible { text-decoration: underline; text-underline-offset: 0.18em; }
.pdp__title {
  margin: 0 0 0.625rem; font-family: var(--font-serif); font-weight: 400;
  font-size: clamp(2.125rem, 4.2vw, 3.25rem); line-height: 1.04; letter-spacing: -0.025em;
  overflow-wrap: break-word;
}
.pdp__brand { margin: 0 0 0.75rem; font-size: 0.9375rem; }
.pdp__brand a { color: var(--ink); text-decoration: none; border-bottom: 1.5px solid var(--brick); }
.pdp__brand a:hover, .pdp__brand a:focus-visible { color: var(--brick); }
.pdp__where { color: var(--ink-faint); }
.pdp__chosen { margin: 0 0 0.15rem; color: var(--ink-muted); font-size: 0.9rem; }

.pdp__pricerow {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 0.5rem 1.125rem;
  margin: 0.75rem 0 0; padding-bottom: 0.875rem; border-bottom: 1.5px solid var(--ink);
}
.pdp__price { font-family: var(--font-slab); font-size: 2.75rem; line-height: 0.9; font-variant-numeric: tabular-nums; }
.pdp__price--none { font-family: var(--font-ui); font-size: 1rem; color: var(--ink-muted); }
/* --in-stock green is for stock status and nothing else. Without the modifier
   the row is ink, so "out of stock" is never carried by colour alone. */
.pdp__stock { font-size: 0.75rem; font-weight: 600; letter-spacing: 0.11em; text-transform: uppercase; color: var(--ink); }
.pdp__stock.is-in { color: var(--in-stock); }
.pdp__rating { margin: 0.75rem 0 0; color: var(--ink-muted); font-size: 0.925rem; }
.pdp__buy { margin: 1.5rem 0 0.75rem; }
.pdp__promoted { margin: 1.5rem 0 0.5rem; }
.pdp__promoted + .pdp__buy { margin-top: 0.5rem; }
.pdp__note { margin: 0 0 1.25rem; color: var(--ink-muted); font-size: 0.8125rem; line-height: 1.6; max-width: 60ch; }
.placements-note {
  margin: 0 0 1rem;
  color: var(--ink-muted);
  font-size: 0.8125rem;
  line-height: 1.6;
  max-width: 60ch;
}

/* Highlights + Description, full width under both columns. */
.pdp__band {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 3rem;
  border-top: 1.5px solid var(--ink); margin-top: 3rem; padding-top: 1.25rem;
}
.pdp__col { min-width: 0; }
/* Explicit size: a UA-default h2 would outrank .pdp__title at narrow widths. */
.pdp__h2 { font-family: var(--font-slab); font-weight: 400; font-size: 1.0625rem; margin: 0 0 0.375rem; }
.pdp__caption { margin: 0 0 1rem; font-size: 0.8125rem; color: var(--ink-muted); }
.pdp__caption--em { font-style: italic; margin: 1rem 0 0; }
.pdp__features { margin: 0; padding: 0; list-style: none; }
.pdp__features li {
  position: relative; padding: 0.625rem 0 0.625rem 1.5rem;
  border-bottom: 1px solid var(--rule-soft); font-size: 0.9375rem;
}
.pdp__features li::before { content: "\2605" / ""; position: absolute; inset-inline-start: 0; color: var(--brick); font-size: 0.75rem; }
.pdp__desc { margin: 0; font-family: var(--font-serif); font-size: 1.1875rem; line-height: 1.6; color: var(--ink-body); max-width: 64ch; white-space: pre-line; }
@media (max-width: 48rem) {
  .pdp { gap: 1.5rem; }
  .pdp__band { grid-template-columns: minmax(0, 1fr); gap: 1.75rem; margin-top: 2rem; }
}

/* --- "how this one qualifies" --------------------------------------------- */

.qual { border: 1.5px solid var(--ink); background: var(--paper-panel); margin-top: 1.5rem; }
.qual__head {
  margin: 0; padding: 0.625rem 1rem; border-bottom: 1.5px solid var(--ink);
  font-size: 0.6875rem; font-weight: 700; letter-spacing: 0.18em; text-transform: uppercase;
}
.qual__rows { display: grid; grid-template-columns: 6.5rem minmax(0, 1fr); margin: 0; padding: 0.25rem 1rem 0.875rem; }
.qual__rows dt {
  padding-top: 0.75rem; font-size: 0.6875rem; font-weight: 600;
  letter-spacing: 0.11em; text-transform: uppercase; color: var(--ink-muted);
}
.qual__rows dd { margin: 0; padding-top: 0.75rem; font-size: 0.9375rem; min-width: 0; }
.qual__src { color: var(--brick); text-decoration: underline; text-underline-offset: 0.15em; }

/* --- brand band ----------------------------------------------------------- */

.bband { padding-block: 0; }  /* see .makers */
.bband__grid { display: grid; grid-template-columns: 26rem minmax(0, 1fr); gap: 2.5rem; align-items: center; padding-block: 2.25rem; }
.bband__grid .ph { border: 0; border-inline-end: 1px solid var(--rule); min-height: 300px; }
.bband__title { margin: 0 0 0.75rem; font-family: var(--font-serif); font-weight: 400; font-size: clamp(1.625rem, 3vw, 2.5rem); line-height: 1.1; letter-spacing: -0.02em; }
.bband__note { margin: 0 0 1.25rem; max-width: 56ch; color: var(--ink-body); font-size: 0.9375rem; }
.bband__acts { display: flex; flex-wrap: wrap; gap: 0.75rem; margin: 0; }
.btn--outline { background: transparent; border-color: var(--ink); color: var(--ink); }
.btn--outline:hover, .btn--outline:focus-visible { background: var(--ink); border-color: var(--ink); color: var(--paper); }
@media (max-width: 62rem) {
  .bband__grid { grid-template-columns: minmax(0, 1fr); gap: 1.5rem; }
  .bband__grid .ph { border-inline-end: 0; border-bottom: 1px solid var(--rule); min-height: 200px; }
}

/* --- breadcrumb ----------------------------------------------------------- */

.crumbs { margin-top: 1.25rem; }
.crumbs__list {
  display: flex; flex-wrap: wrap; align-items: center; list-style: none; margin: 0; padding: 0;
  font-size: 0.75rem; font-weight: 600; letter-spacing: 0.09em; text-transform: uppercase;
}
/* 2.5.8 on SIZE, not the spacing exception and not the inline-prose one:
   these are not inside a sentence, so the prose carve-out does not apply, and
   measured at 76 x 24 they clear the 24x24 minimum outright. (The 4.5px gap
   would NOT be enough to pass on spacing, so do not switch the basis.) */
.crumbs__list a { display: inline-flex; align-items: center; min-height: 24px; color: var(--ink-faint); text-decoration: none; }
.crumbs__list a:hover, .crumbs__list a:focus-visible { color: var(--brick); text-decoration: underline; text-underline-offset: 0.18em; }
.crumbs__list li + li::before { content: "/"; margin-inline: 0.5rem; color: var(--rule-chip); }
.crumbs__list [aria-current="page"] { color: var(--ink); }

/* --- mobile sticky buy bar ------------------------------------------------ */

.buybar { display: none; }
@media (max-width: 48rem) {
  /* The bar is ~81px. Both of these are required and they fix different
     things: the padding keeps the bar from sitting on the last of the page's
     own content, and scroll-padding-bottom keeps a forward-Tab from parking a
     focused element behind it. The scroll-into-view algorithm short-circuits
     at the document's scroll end, which is exactly where bar and content meet,
     so neither substitutes for the other. */
  .pdppage { padding-block-end: 81px; }
  .buybar {
    position: sticky; bottom: 0; z-index: 20;
    display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 0.875rem; align-items: center;
    margin-inline: calc(50% - 50vw); padding: 0.75rem calc(50vw - 50%);
    background: var(--paper); border-top: 1.5px solid var(--ink);
  }
  .buybar__price { font-family: var(--font-slab); font-size: 1.375rem; font-variant-numeric: tabular-nums; }
  .buybar__act { display: flex; flex-direction: column; align-items: stretch; gap: 0.35rem; min-width: 0; }
  .buybar .pdp__promoted { margin: 0; }
  .buybar__btn { width: 100%; }
}
/* When the buy bar carries a Promoted label it is taller than 81px; bump the
   reservation so a focused control does not park behind it (2.4.11).
   :has matches display:none nodes, and this selector is more specific than
   html:has(.consent), so exclude the consent case: pre-consent the banner
   hides the bar and needs the larger 16rem reserve. */
@media (max-width: 48rem) {
  .pdppage:has(.buybar .pdp__promoted) { padding-block-end: 110px; }
  html:has(.buybar .pdp__promoted):not(:has(.consent)) { scroll-padding-bottom: 118px; }
}
/* Applied at every width, not only where a bar exists: on a page without one
   the only effect is a little slack under a fragment jump. */
@media (max-width: 48rem) { html { scroll-padding-bottom: 89px; } }

.btn--lg { min-height: 52px; padding: 0.75rem 1.5rem; font-size: 0.8125rem; justify-content: center; }
@media (max-width: 30rem) { .pdp__buy .btn--lg { width: 100%; } }

@media (forced-colors: active) {
  .pdp__hero { border: 1px solid CanvasText; }
  .gal__thumb { border-color: ButtonBorder; }
  .gal__thumb.is-current { forced-color-adjust: none; border-color: Highlight; }
  .opt__val { border-color: ButtonBorder; }
  .opt__val.is-current { forced-color-adjust: none; background: Highlight; color: HighlightText; border-color: Highlight; }
  .searchform__input { border-color: CanvasText; }
}

/* Shop tag: a Texas retailer that carries other makers rather than making its
   own goods. Text, never colour alone, so it survives greyscale and forced
   colours. Sits inside a heading, so it must not inherit heading weight. */
.tag {
  display: inline-block; vertical-align: middle;
  font-size: 0.7rem; font-weight: 700; line-height: 1;
  text-transform: uppercase; letter-spacing: 0.06em;
  padding: 0.3rem 0.45rem; border-radius: 4px;
  border: 1px solid var(--line); color: var(--ink-soft); background: var(--paper);
  white-space: nowrap;
}
.bcard__title .tag, .page__title .tag { margin-inline-start: 0.4rem; }
@media (forced-colors: active) { .tag { border-color: CanvasText; } }

/* --- guides (editorial layer, 2026-09-02) -------------------------------- */

/* Prose sits on the same measure as page__intro. Compare blocks keep the
   product grid but cap it at two-up so four cards do not collapse to
   postage stamps inside 46rem. */
.guide, .guide-index { max-width: 46rem; }
.guide .page__title { margin-bottom: 0.25rem; }
.guide > .guide__byline {
  color: var(--ink-faint); font-size: 0.9rem; margin: 0 0 1.5rem;
}
.guide__byline-sep::before { content: "\00b7"; margin-inline: 0.45em; color: var(--rule-chip); }
/* Direct children only: a descendant `.guide h3` / `.guide p` / `.guide ul`
   would beat `.card__title`, `.card__price`, `.grid`, and the byline. Compare
   cards keep listing-page styles. The compare h2 is a section heading, so it
   is listed with the article h2s. */
.guide > h2, .guide__compare > h2 {
  font-family: var(--font-slab); font-weight: 400; font-size: 1.375rem;
  margin: 1.75rem 0 0.75rem;
}
.guide > h3 {
  font-family: var(--font-ui); font-weight: 700; font-size: 1.05rem;
  margin: 1.25rem 0 0.5rem;
}
.guide > p:not(.crumb):not(.guide__byline),
.guide > ul li, .guide > ol li {
  color: var(--ink-body); max-width: 46rem;
}
.guide > ul, .guide > ol { margin: 0 0 1rem; padding-inline-start: 1.25rem; }
.guide > ul li, .guide > ol li { margin: 0 0 0.35rem; }
.guide > p:not(.crumb):not(.guide__byline) { margin: 0 0 1rem; }

.guide-list { list-style: none; margin: 1.5rem 0 0; padding: 0; }
.guide-list__item { margin: 0 0 1.5rem; padding-bottom: 1.25rem; border-bottom: 1px solid var(--rule-soft); }
.guide-list__item > a {
  font-family: var(--font-slab); font-weight: 400; font-size: 1.25rem;
  color: var(--ink); text-decoration: none;
}
.guide-list__item > a:hover, .guide-list__item > a:focus-visible {
  color: var(--brick); text-decoration: underline; text-underline-offset: 0.18em;
}
.guide-list__item p { margin: 0.4rem 0 0; color: var(--ink-soft); max-width: 46rem; }
.guide-list__date, .guide-index__feed {
  margin: 0.35rem 0 0; font-size: 0.75rem; font-weight: 700;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-faint);
}
.guide-index__feed { margin-top: 1.5rem; }

.guide__compare { margin: 2rem 0; }
.guide__compare > h2 { margin-top: 0; }
.guide__compare-note, .guide__compare-empty { color: var(--ink-soft); font-size: 0.925rem; }
.guide__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 40rem) {
  .guide__grid { grid-template-columns: minmax(0, 1fr); }
}

/* Keyboard-operable table scroll region. tabindex=0 is the accepted
   trade-off recorded in .claude/accessibility.md: no-JS users cannot
   scroll an overflowing table without a tab stop (WCAG 2.1.1). */
.table-scroll {
  overflow-x: auto; margin: 1rem 0 1.5rem;
  border: 1px solid var(--rule-chip);
}
.guide table { width: 100%; border-collapse: collapse; font-size: 0.925rem; }
.guide caption {
  text-align: start; font-weight: 700; font-size: 0.75rem;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-faint); padding: 0.65rem 0.75rem 0.35rem;
  caption-side: top;
}
.guide th, .guide td {
  text-align: start; vertical-align: top;
  padding: 0.55rem 0.75rem; border-top: 1px solid var(--rule-soft);
  color: var(--ink-body);
}
.guide thead th { border-top: 0; color: var(--ink); font-weight: 700; }
.guide tbody th { font-weight: 700; color: var(--ink); }

