/* ============================================================================
   AIRSTRYK — Design tokens
   Direction: BENCH & BONE. A near-black room, one warm light, and every object
   laid out on bone-coloured paper where you can actually read it.
   Dark = desire. Bone = understanding.
   Geometry: every angled cut is 3:8 (0.375) — the measured taper of the
   chevron in their own mark. 45° is banned.
   ========================================================================= */

/* --- Fonts: self-hosted, latin subset ------------------------------------ */
@font-face {
  font-family: 'Archivo';
  src: url('/fonts/archivo-var.woff2?v=510b8100') format('woff2-variations');
  font-weight: 400 800;
  font-stretch: 62% 125%;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Plex Mono';
  src: url('/fonts/plexmono-400.woff2?v=2c9bacf0') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Plex Mono';
  src: url('/fonts/plexmono-500.woff2?v=3a2668b9') format('woff2');
  font-weight: 500; font-style: normal; font-display: swap;
}

:root {
  /* --- Ground ----------------------------------------------------------- */
  --bg:           #0A0A0B;
  --surface-1:    #121316;
  --surface-2:    #1A1B1F;
  --surface-sunk: #060607;

  /* --- Bone: the plate ------------------------------------------------- */
  --bone:      #EDE9E0;
  --bone-hi:   #F5F2EC;
  --bone-lo:   #DED9CE;
  --bone-line: #CFC9BC;

  /* --- Rules ----------------------------------------------------------- */
  --rule-01: #1E2024;   /* decorative dividers   */
  --rule-02: #2A2C31;   /* card + plate borders  */
  --rule-03: #3E4249;   /* emphasis / hover      */
  --border-field: #6E7178; /* functional borders — 4.05:1, meets 3:1 UI */

  /* --- Type on dark ---------------------------------------------------- */
  --text:           #EDE9E0;  /* 16.33:1 */
  --text-secondary: #BFBAB0;  /* 10.24:1 */
  --text-muted:     #949089;  /*  6.23:1 */
  --text-faint:     #7C7972;  /*  4.56:1 — the faintest step that is still
                                  TEXT. A token named --text-* must pass AA at
                                  body size; anything below that is a rule
                                  colour and belongs in --rule-*. */

  /* That 4.56:1 is against --bg, and --text-faint is used mostly on the raised
     surfaces, where it does not clear AA: measured in the browser it is 4.28:1
     on --surface-1 and 3.96:1 on --surface-2. The card-field placeholders, the
     checkout's per-unit price and the chosen SKU all sit there, so those take
     this step instead — the same device --danger-on-s2 already uses. 5.18:1 on
     --surface-1, 4.80:1 on --surface-2, and still a clear step below
     --text-muted, so the hierarchy the design signed off survives. */
  --text-faint-on-s2: #8A8780;

  /* --- Type on bone ---------------------------------------------------- */
  --ink:        #15161A;  /* 14.92:1 on bone */
  --ink-muted:  #5A5750;  /*  5.95:1 on bone */
  --ink-faint:  #656158;  /*  5.09:1 on bone — the faintest ink that is still
                              text. --bone-line is a HAIRLINE colour at 1.36:1
                              and must never be mapped to a text token. */
  --ink-accent: #7E4F14;  /*  5.75:1 on bone */

  /* --- Accent: oxide bronze. The average of their own anodising. -------
     Not blaze orange: in airgun retail a bright orange muzzle marker is the
     federal signal for a toy gun, which is the exact read the brand forbids. */
  --accent:       #C1832F;  /* 6.18:1 on --bg */
  --accent-hover: #D89438;  /* 7.74:1 */
  --accent-press: #9E661F;  /* 4.12:1 — fills and borders only */
  --accent-ink:   #0A0A0B;  /* text on accent fill — 6.18:1 */
  --accent-wash:  rgba(193, 131, 47, .10);

  /* --- Semantic -------------------------------------------------------- */
  --success: #7CA85F;  /* 7.18:1  */
  --warning: #DFC85E;  /* 11.84:1 */
  --danger:  #D9553C;  /* 5.01:1  */
  --danger-on-s2: #E0614A; /* 4.90:1 on --surface-2 */

  /* --- Type: one family, two axes, three voices ------------------------ */
  --font: 'Archivo', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-mono: 'Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  /* Width axis carries emphasis where a lesser system reaches for colour.
     The wordmark runs ~36% wider per character than a default grotesque. */
  --wdth-display: 112;
  --wdth-ui: 104;
  --wdth-text: 100;

  /* Fluid 390px → 1440px, root 16px */
  --fs-display-1: clamp(2.875rem, 1.436rem + 5.905vw, 6.75rem);    /*  46 → 108 */
  --fs-display-2: clamp(2.375rem, 1.493rem + 3.619vw, 4.75rem);    /*  38 →  76 */
  --fs-h1:        clamp(2rem,     1.489rem + 2.095vw, 3.375rem);   /*  32 →  54 */
  --fs-h2:        clamp(1.625rem, 1.300rem + 1.333vw, 2.5rem);     /*  26 →  40 */
  --fs-h3:        clamp(1.3125rem, 1.150rem + 0.667vw, 1.75rem);   /*  21 →  28 */
  --fs-h4:        clamp(1.125rem, 1.055rem + 0.286vw, 1.3125rem);  /*  18 →  21 */
  --fs-quote:     clamp(1.375rem, 1.096rem + 1.143vw, 2.125rem);   /*  22 →  34 */
  --fs-price:     clamp(1.375rem, 1.189rem + 0.762vw, 1.875rem);   /*  22 →  30 */
  --fs-body-lg:   clamp(1.0625rem, 1.016rem + 0.190vw, 1.1875rem); /*  17 →  19 */
  --fs-body:      clamp(1rem, 0.977rem + 0.095vw, 1.0625rem);      /*  16 →  17 */
  --fs-body-sm:   clamp(0.9063rem, 0.895rem + 0.048vw, 0.9375rem); /* 14.5 → 15 */
  --fs-data:      clamp(0.8125rem, 0.789rem + 0.095vw, 0.875rem);  /*  13 →  14 */
  --fs-nav:       clamp(0.8125rem, 0.789rem + 0.095vw, 0.875rem);  /*  13 →  14 */
  --fs-label:     clamp(0.6875rem, 0.653rem + 0.143vw, 0.7813rem); /*  11 → 12.5 */

  /* --- Shell ----------------------------------------------------------- */
  --shell:   1680px;
  --grid:    1296px;
  --narrow:  792px;
  --measure: 68ch;
  --gutter:    clamp(1.25rem, 0.600rem + 2.667vw, 3rem);  /* 20 → 48 */
  --grid-gap:  clamp(1rem, 0.629rem + 1.524vw, 2rem);     /* 16 → 32 */
  --hdr-h:    76px;   /* the header bar itself */
  --topbar-h: 34px;   /* the podcast strip above it */
  /* --header-h is the TOTAL fixed chrome. Every page offset, sticky top and
     scroll-padding in the site is written against it, so defining it as the
     sum means adding the strip needed no edits anywhere else. */
  --header-h: calc(var(--topbar-h) + var(--hdr-h));

  /* --- Space: 8px rhythm, 4px half-step. Only these values ship. ------- */
  --s-1: 4px;   --s-2: 8px;   --s-3: 12px;  --s-4: 16px;  --s-5: 24px;
  --s-6: 32px;  --s-7: 40px;  --s-8: 56px;  --s-9: 72px;  --s-10: 96px;
  --s-11: 128px; --s-12: 160px; --s-13: 208px;

  --space-section:    clamp(4.5rem, 2.457rem + 8.381vw, 10rem);   /*  72 → 160 */
  --space-section-lg: clamp(6rem, 3.400rem + 10.667vw, 13rem);    /*  96 → 208 */
  --space-block:      clamp(2.5rem, 1.757rem + 3.048vw, 4.5rem);  /*  40 →  72 */

  /* --- The 3:8 cut. Two opposite corners only: TL and BR. -------------- */
  --cut-sm-x: 6px;  --cut-sm-y: 16px;
  --cut-md-x: 9px;  --cut-md-y: 24px;
  --cut-lg-x: 12px; --cut-lg-y: 32px;
  --cut-xl-x: 18px; --cut-xl-y: 48px;

  /* --- Motion ---------------------------------------------------------- */
  --dur-tint: 120ms;
  --dur-hover: 180ms;
  --dur-move: 240ms;
  --dur-plate: 320ms;
  --dur-reveal: 480ms;
  --dur-wipe: 640ms;
  --ease-entrance:   cubic-bezier(.16, 1, .3, 1);
  --ease-standard:   cubic-bezier(.4, 0, .2, 1);
  --ease-exit:       cubic-bezier(.4, 0, 1, 1);
  --ease-mechanical: cubic-bezier(.65, 0, .35, 1);

  /* --- Elevation ------------------------------------------------------- */
  --shadow-plate: 0 24px 48px -24px rgba(0, 0, 0, .85);
  --shadow-plate-hi: 0 32px 56px -24px rgba(0, 0, 0, .9);
  --shadow-card: 0 20px 40px -28px rgba(0, 0, 0, .8);
}

/* ============================================================================
   AIRSTRYK — Reset, typography, layout primitives
   ========================================================================= */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + var(--s-5));
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--fs-body);
  font-variation-settings: 'wdth' var(--wdth-text), 'wght' 400;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

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

/* Radius is zero everywhere. The 3:8 cut is the only corner treatment. */
input, textarea, select, button { font: inherit; color: inherit; border-radius: 0; }
button { background: none; border: 0; cursor: pointer; }

a { color: inherit; text-decoration: none; }

/* --- Focus: never removed, never colour-only ---------------------------- */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
:focus:not(:focus-visible) { outline: none; }

::selection { background: var(--accent); color: var(--accent-ink); }

.skip {
  position: absolute; left: var(--s-4); top: -100px; z-index: 200;
  padding: var(--s-3) var(--s-5);
  background: var(--accent); color: var(--accent-ink);
  font-size: var(--fs-nav); font-weight: 600; text-transform: uppercase;
  letter-spacing: .06em;
  transition: top var(--dur-move) var(--ease-entrance);
}
.skip:focus { top: var(--s-4); }

.sr {
  position: absolute; width: 1px; height: 1px; padding: 0;
  margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ==========================================================================
   TYPOGRAPHY
   Hierarchy comes from scale, case+tracking, width, and asymmetric space.
   Nothing else.
   ========================================================================= */

.display-1, .display-2, h1, h2, h3, h4 {
  font-family: var(--font);
  text-wrap: balance;
}

.display-1 {
  font-size: var(--fs-display-1);
  font-variation-settings: 'wdth' var(--wdth-display), 'wght' 700;
  text-transform: uppercase;
  letter-spacing: -.015em;
  line-height: .92;
  max-width: 22ch;
}
.display-2 {
  font-size: var(--fs-display-2);
  font-variation-settings: 'wdth' var(--wdth-display), 'wght' 700;
  text-transform: uppercase;
  letter-spacing: -.01em;
  line-height: .95;
  max-width: 26ch;
}
h1, .h1 {
  font-size: var(--fs-h1);
  font-variation-settings: 'wdth' 108, 'wght' 700;
  text-transform: uppercase;
  letter-spacing: 0;
  line-height: 1.02;
  max-width: 30ch;
}
h2, .h2 {
  font-size: var(--fs-h2);
  font-variation-settings: 'wdth' 106, 'wght' 600;
  text-transform: uppercase;
  letter-spacing: .01em;
  line-height: 1.08;
  max-width: 34ch;
}
h3, .h3 {
  font-size: var(--fs-h3);
  font-variation-settings: 'wdth' var(--wdth-ui), 'wght' 600;
  letter-spacing: .005em;
  line-height: 1.20;
  max-width: 40ch;
}
h4, .h4 {
  font-size: var(--fs-h4);
  font-variation-settings: 'wdth' var(--wdth-text), 'wght' 600;
  line-height: 1.30;
  max-width: 48ch;
}

p { max-width: var(--measure); text-wrap: pretty; }

.deck {
  font-size: var(--fs-body-lg);
  color: var(--text-secondary);
  line-height: 1.55;
  max-width: 62ch;
}
.small { font-size: var(--fs-body-sm); letter-spacing: .005em; line-height: 1.55; }
.muted { color: var(--text-muted); }
.secondary { color: var(--text-secondary); }

/* Eyebrow: the wide tracking is the typographic echo of the wordmark
   being wider than the emblem. */
.eyebrow {
  display: block;
  font-size: var(--fs-label);
  font-variation-settings: 'wdth' 106, 'wght' 600;
  text-transform: uppercase;
  letter-spacing: .16em;
  line-height: 1;
  color: var(--accent);
  max-width: 28ch;
  /* "TEXAS-LEGAL, .30 AND" over a lone "UP" — even the two lines out. */
  text-wrap: balance;
}
.eyebrow--muted { color: var(--text-muted); }

/* Data: numbers that are facts */
.data, .mono {
  font-family: var(--font-mono);
  font-size: var(--fs-data);
  font-variant-numeric: tabular-nums;
  letter-spacing: .01em;
  line-height: 1.45;
}
.data-label {
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: var(--fs-data);
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--text-muted);
}
/* Units sit at 0.85em in muted immediately after the value */
.u { font-size: .85em; color: var(--text-muted); margin-left: .15em; }

.price {
  font-size: var(--fs-price);
  font-variation-settings: 'wdth' var(--wdth-ui), 'wght' 600;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

/* All prices, spec values and table figures. Non-negotiable. */
table, .price, .data, .mono, .spec { font-variant-numeric: tabular-nums; }

em, i { font-style: italic; }
strong, b { font-variation-settings: 'wdth' var(--wdth-text), 'wght' 600; }

/* Prose block for long-form on bone */
.prose > * + * { margin-top: var(--s-5); }
.prose h2 { margin-top: var(--s-9); }
.prose h3 { margin-top: var(--s-7); }
.prose h2 + *, .prose h3 + * { margin-top: var(--s-4); }
.prose ul, .prose ol { padding-left: var(--s-5); max-width: var(--measure); }
.prose li + li { margin-top: var(--s-2); }
/* The ambient accent, not the bone one.
   Hard-coding `--ink-accent` here was right for `.section--bone`, which is
   where `.prose` was first used, and wrong everywhere else: on the near-black
   sections the four policy pages use, eleven links sat at 2.84:1 — the only
   text that failed AA anywhere on the storefront — and on hover they went to
   `--ink` at 1.09:1, which is a link, underline and all, disappearing under the
   cursor. Reading the ambient token means the remap on `.section--bone` keeps
   resolving these to exactly what they were, and the dark sections get
   6.18:1 and 7.74:1. */
.prose a {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition: color var(--dur-hover) var(--ease-standard);
}
.prose a:hover { color: var(--accent-hover); }

/* ==========================================================================
   LAYOUT
   ========================================================================= */

.shell { max-width: var(--shell); margin-inline: auto; }

.wrap {
  width: 100%;
  max-width: var(--grid);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.wrap--narrow { max-width: calc(var(--narrow) + var(--gutter) * 2); }

.section { padding-block: var(--space-section); position: relative; }
.section--lg { padding-block: var(--space-section-lg); }
.section--tight { padding-block: var(--space-block); }
.section--sunk { background: var(--surface-sunk); }
.section--s1 { background: var(--surface-1); }

/* Phones: the same rhythm at a smaller step. 72px above and below every band,
   and 40px under every heading, left a phone scrolling a screen of empty ground
   for every two screens of content. The tokens are re-declared on the shop's
   own bands rather than on :root because the portal loads this stylesheet and
   lays itself out with --space-block — this must not move it. */
@media (max-width: 639px) {
  .section, .hero, .ftr {
    --space-section: 2.5rem;    /* 72 → 40 */
    --space-section-lg: 3.5rem; /* 96 → 56 */
    --space-block: 2rem;        /* 40 → 32 */
  }
}

/* Bone surface: understanding. Long-form and teaching live here.

   Themed by TOKEN REMAP, not by descendant selectors. Repainting text with
   `.section--bone h3 { color: var(--ink) }` breaks the moment a dark card is
   nested inside a bone section — the card inherits ink and its text vanishes
   against a dark ground. That happened on the home page, on /air-and-ammo/ and
   in the selector results, and each was patched separately.

   Remapping the tokens instead means every component keeps reading var(--text)
   and is correct on any surface, at any depth. */
.section--bone {
  --text: var(--ink);
  --text-secondary: var(--ink-muted);
  --text-muted: var(--ink-muted);
  --text-faint: var(--ink-faint);
  --text-faint-on-s2: var(--ink-faint);  /* clears AA on bone unaided */
  --accent: var(--ink-accent);          /* #7E4F14 — the AA-safe bronze on bone */
  --accent-hover: var(--ink);
  --accent-ink: var(--bone);
  --accent-wash: rgba(126, 79, 20, .07);
  --rule-01: var(--bone-line);
  --rule-02: var(--bone-line);
  --rule-03: var(--ink-muted);
  --border-field: var(--ink-muted);
  --surface-1: var(--bone-hi);
  --surface-2: var(--bone);
  background: var(--bone);
  color: var(--text);
}

/* A dark island inside a bone section re-declares the dark palette. One rule,
   every component, forever. */
.section--bone .framed__in,
.section--bone .sel--res,
.section--bone .pdp__panel,
.section--bone .pcard__media,
.section--bone .plate--on-dark {
  --text: #EDE9E0;
  --text-secondary: #BFBAB0;
  --text-muted: #949089;
  --text-faint: #7C7972;
  --text-faint-on-s2: #8A8780;
  --accent: #C1832F;
  --accent-hover: #D89438;
  --accent-ink: #0A0A0B;
  --accent-wash: rgba(193, 131, 47, .10);
  --rule-01: #1E2024;
  --rule-02: #2A2C31;
  --rule-03: #3E4249;
  --border-field: #6E7178;
  --surface-1: #121316;
  --surface-2: #1A1B1F;
  color: var(--text);
}

/* Section rhythm: the cadence never varies.
   Space above a heading is 3× the space below it. */
.head { margin-bottom: var(--space-block); }
.head .eyebrow { margin-bottom: var(--s-4); }
.head .rule { margin-bottom: var(--s-6); }
.head .deck { margin-top: var(--s-5); }
@media (max-width: 639px) {
  .head { margin-bottom: var(--s-5); }
  .head .eyebrow { margin-bottom: var(--s-2); }
  .head .rule { margin-bottom: var(--s-4); }
  .head .deck { margin-top: var(--s-4); }
}

.grid { display: grid; gap: var(--grid-gap); }
.g-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.g-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.g-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

@media (max-width: 1023px) {
  .g-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .g-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 639px) {
  .g-2, .g-3, .g-4 { grid-template-columns: minmax(0, 1fr); }
}

.stack { display: flex; flex-direction: column; }
.stack-2 > * + * { margin-top: var(--s-2); }
.stack-3 > * + * { margin-top: var(--s-3); }
.stack-4 > * + * { margin-top: var(--s-4); }
.stack-5 > * + * { margin-top: var(--s-5); }
.stack-6 > * + * { margin-top: var(--s-6); }
.stack-7 > * + * { margin-top: var(--s-7); }

.row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-4); }
.row--between { justify-content: space-between; }

/* Offset, never centred. A section may start at column 3 of 12. */
.offset-3 { margin-left: calc(100% / 12 * 2); }
@media (max-width: 1023px) { .offset-3 { margin-left: 0; } }

/* ============================================================================
   AIRSTRYK — Signature devices and components
   Every angled cut is 3:8. Two opposite corners only (TL / BR) — two corners
   read as an arrowhead, four read as a hex sticker.
   ========================================================================= */

/* --- The 3:8 cut -------------------------------------------------------- */
.cut   { --cx: var(--cut-md-x); --cy: var(--cut-md-y); }
.cut-s { --cx: var(--cut-sm-x); --cy: var(--cut-sm-y); }
.cut-l { --cx: var(--cut-lg-x); --cy: var(--cut-lg-y); }
.cut-x { --cx: var(--cut-xl-x); --cy: var(--cut-xl-y); }

.cut, .cut-s, .cut-l, .cut-x {
  clip-path: polygon(
    0 var(--cy), var(--cx) 0, 100% 0,
    100% calc(100% - var(--cy)), calc(100% - var(--cx)) 100%, 0 100%
  );
}

/* Bordered + cut: the border must follow the cut, so it is a 1px outer
   layer rather than a border property. */
.framed {
  --cx: var(--cut-md-x); --cy: var(--cut-md-y);
  position: relative;
  padding: 1px;
  background: var(--rule-02);
  clip-path: polygon(
    0 var(--cy), var(--cx) 0, 100% 0,
    100% calc(100% - var(--cy)), calc(100% - var(--cx)) 100%, 0 100%
  );
  transition: background var(--dur-hover) var(--ease-standard);
}
.framed > .framed__in {
  height: 100%;
  background: var(--surface-1);
  clip-path: polygon(
    0 var(--cy), var(--cx) 0, 100% 0,
    100% calc(100% - var(--cy)), calc(100% - var(--cx)) 100%, 0 100%
  );
}
a.framed:hover, .framed:hover { background: var(--rule-03); }

/* --- Terminated hairline ------------------------------------------------
   A rule never fades out and never runs to the viewport edge. It ends in a
   1px × 8px tick, like the terminus of a machinist's scale. */
.rule {
  position: relative;
  height: 1px;
  background: var(--rule-02);
  transform-origin: left;
}
.rule::after {
  content: "";
  position: absolute; right: 0; top: -4px;
  width: 1px; height: 8px;
  background: var(--rule-03);
}

/* Accent variant — once per page maximum. The tick becomes the truncated
   chevron apex: 14px × 2px, 14.4% of the chevron's width. */
.rule--accent { background: var(--accent); }
.rule--accent::after {
  width: 14px; height: 2px; top: -1px;
  background: var(--accent);
}

/* --- Knockout rule -----------------------------------------------------
   A hairline passes behind a label and knocks out around it — the logo's
   9px gap where the wordmark meets the chevron, generalised. */
.rule-through {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: 0;
}
.rule-through--center { grid-template-columns: 1fr auto 1fr; }
.rule-through::before, .rule-through::after {
  content: "";
  height: 1px;
  background: var(--rule-02);
}
.rule-through::before { display: none; }
.rule-through--center::before { display: block; }
.rule-through > * { padding-inline: max(.12em, 2px) .75em; }
.rule-through--center > * { padding-inline: .75em; }
.section--bone .rule-through::before,
.section--bone .rule-through::after { background: var(--bone-line); }

/* --- Buttons ----------------------------------------------------------- */
.btn {
  --cx: var(--cut-sm-x); --cy: var(--cut-sm-y);
  display: inline-flex; align-items: center; gap: var(--s-2);
  padding: var(--s-4) var(--s-6);
  font-size: var(--fs-nav);
  font-variation-settings: 'wdth' var(--wdth-ui), 'wght' 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  line-height: 1.15;
  cursor: pointer;
  /* Balanced wrapping rather than nowrap: nowrap pushed long labels such as
     "Not sure which? Speak to a specialist" 85px past the viewport at 320px. */
  white-space: normal;
  text-wrap: balance;
  max-width: 100%;
  clip-path: polygon(
    0 var(--cy), var(--cx) 0, 100% 0,
    100% calc(100% - var(--cy)), calc(100% - var(--cx)) 100%, 0 100%
  );
  transition: background var(--dur-tint) var(--ease-standard),
              color var(--dur-tint) var(--ease-standard),
              transform 90ms var(--ease-exit);
}
.btn:active { transform: scale(.985); }

/* A button that cannot be pressed must not look identical to one that can.
   Never colour alone: the press feedback goes too, the cursor says so, and in
   every place this state is used there is a line of text next to it saying what
   is still needed. */
.btn:disabled,
.btn[aria-disabled="true"] { opacity: .42; cursor: not-allowed; }
.btn:disabled:active,
.btn[aria-disabled="true"]:active { transform: none; }

.btn--primary { background: var(--accent); color: var(--accent-ink); }
.btn--primary:hover { background: var(--accent-hover); }

.btn--ghost {
  background: transparent;
  color: var(--text);
  box-shadow: inset 0 0 0 1px var(--border-field);
}
.btn--ghost:hover { background: var(--surface-2); color: var(--text); }

.btn--bone { background: var(--ink); color: var(--bone); }
.btn--bone:hover { background: #000; }

.btn--sm { padding: var(--s-3) var(--s-4); font-size: var(--fs-label); }

/* Link with a chevron tick that extends on hover */
.tlink {
  display: inline-flex; align-items: center; gap: var(--s-2);
  font-size: var(--fs-nav);
  font-variation-settings: 'wdth' var(--wdth-ui), 'wght' 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--accent);
  transition: color var(--dur-hover) var(--ease-standard);
}
.tlink::after {
  content: "";
  width: 14px; height: 2px;
  background: currentColor;
  transition: width var(--dur-move) var(--ease-standard);
}
.tlink:hover { color: var(--accent-hover); }
.tlink:hover::after { width: 24px; }

/* ==========================================================================
   BONE PLATE — the specimen plate.
   White studio backgrounds are not a defect to be keyed out; they become the
   deliberate second surface of the system. mix-blend-mode: multiply makes any
   white ground disappear into the plate with no halo and no per-image labour.
   ========================================================================= */
.plate {
  --cx: var(--cut-md-x); --cy: var(--cut-md-y);
  /* Must be block. As an inline <span> the aspect-ratio and width below are
     ignored and the plate collapses to 0x0 — which is exactly what happened
     everywhere it was not a flex or grid child. */
  display: block;
  position: relative;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  background: linear-gradient(178deg, var(--bone-hi) 0%, var(--bone) 46%, var(--bone-lo) 100%);
  box-shadow: inset 0 1px 0 rgba(237, 233, 224, .06), var(--shadow-plate);
  clip-path: polygon(
    0 var(--cy), var(--cx) 0, 100% 0,
    100% calc(100% - var(--cy)), calc(100% - var(--cx)) 100%, 0 100%
  );
  transition: box-shadow var(--dur-plate) var(--ease-standard);
}
.plate--tall { aspect-ratio: 4 / 5; }
.plate--wide { aspect-ratio: 16 / 9; }
/* A portrait plate the full width of a phone is 440px of mostly bone. The
   photograph is contained, never cropped, so a shorter frame loses nothing. */
@media (max-width: 639px) { .plate--tall { aspect-ratio: 3 / 2; } }

/* A bone plate on a bone section has no ground to separate from, so it earns a
   hairline and a soft shadow instead of the deep one it uses on dark. */
.section--bone .plate {
  box-shadow: inset 0 0 0 1px var(--bone-line), 0 18px 34px -24px rgba(21, 22, 26, .38);
}
.section--bone .plate::before, .section--bone .plate::after {
  background:
    linear-gradient(var(--ink-muted), var(--ink-muted)) 0 0 / 1px 10px no-repeat,
    linear-gradient(var(--ink-muted), var(--ink-muted)) 0 0 / 10px 1px no-repeat;
  opacity: .45;
}
.section--bone .plate::before { background-position: right top, right top; }
.section--bone .plate::after { background-position: left bottom, left bottom; }

.plate { position: relative; }
.plate img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  padding: 6% 4%;
  box-sizing: border-box;
  object-fit: contain;
  mix-blend-mode: multiply;
  filter: contrast(1.04) saturate(.96);
  transition: transform var(--dur-plate) var(--ease-standard);
}
.plate--lift img { filter: contrast(1.04) saturate(.96) brightness(1.07); }

/* Registration ticks: inset 14px from the two un-cut corners (TR, BL),
   one horizontal and one vertical per corner, not meeting.
   Reads as a reticle mark and a printer's crop — precision with no
   military signifier. */
.plate::before, .plate::after {
  content: "";
  position: absolute;
  pointer-events: none;
  background:
    linear-gradient(var(--bone-line), var(--bone-line)) 0 0 / 1px 10px no-repeat,
    linear-gradient(var(--bone-line), var(--bone-line)) 0 0 / 10px 1px no-repeat;
}
.plate::before { top: 14px; right: 14px; width: 10px; height: 10px;
  background-position: right top, right top; }
.plate::after  { bottom: 14px; left: 14px; width: 10px; height: 10px;
  background-position: left bottom, left bottom; }

/* ==========================================================================
   PRODUCT CARD — the letterbox specimen.
   Rifle cutouts measure 3:1 to 5:1. A square tile wastes the frame and makes
   every rifle look small, so the plate is letterboxed to match the object.
   ========================================================================= */
.pcard { display: block; height: 100%; }
.pcard__media { display: grid; place-items: center; }
.pcard__name { transition: color var(--dur-hover) var(--ease-standard); }
.pcard__name:hover { color: var(--accent); }
.pcard .framed__in { display: flex; flex-direction: column; }

.pcard__media {
  display: block;
  position: relative;
  aspect-ratio: 16 / 7;
  display: grid; place-items: center;
  overflow: hidden;
  background:
    radial-gradient(120% 140% at 50% 118%, rgba(193, 131, 47, .13) 0%, transparent 62%),
    linear-gradient(180deg, var(--surface-2) 0%, var(--surface-1) 100%);
}
/* A percentage HEIGHT on a grid item does not resolve against a parent whose
   height comes from aspect-ratio, so `height: 84%` silently became `auto` and
   every portrait product rendered several times taller than its 16:7 frame —
   then overflow:hidden cropped it to a 120px slice of empty background. That
   is why the tanks, the hand pump and the .357 slugs looked like empty cards.
   Absolute positioning gives the percentages a definite box to resolve
   against, and padding does the inset that the percentages were faking. */
.pcard__media img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  padding: 8% 6%;
  box-sizing: border-box;
  object-fit: contain;
  filter:
    drop-shadow(0 14px 18px rgba(0, 0, 0, .55))
    /* 1px bone rim-light: without it a black rifle cutout on a #121316 card
       is an empty rectangle, and roughly a third of this catalog is black. */
    drop-shadow(0 -1px 0 rgba(237, 233, 224, .16));
  transition: transform var(--dur-plate) var(--ease-standard);
}
.pcard:hover .pcard__media img { transform: scale(1.028); }

.pcard__body {
  flex: 1;
  display: flex; flex-direction: column;
  padding: var(--s-5);
  border-top: 1px solid var(--rule-01);
}
.pcard__name {
  display: block;
  font-size: var(--fs-h4);
  font-variation-settings: 'wdth' var(--wdth-text), 'wght' 600;
  line-height: 1.25;
}
.pcard__kicker {
  margin-top: var(--s-2);
  font-size: var(--fs-body-sm);
  color: var(--text-muted);
  line-height: 1.45;
}
.pcard__foot {
  margin-top: auto; padding-top: var(--s-4);
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-3);
}
.pcard__price {
  font-size: var(--fs-body-lg);
  font-variation-settings: 'wdth' var(--wdth-ui), 'wght' 600;
  font-variant-numeric: tabular-nums;
}
.pcard__vendor {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--text-muted);
  text-align: right;
}

/* --- Compact card and its grid -------------------------------------------
   Photograph, maker, name, price, one button. Two across on a phone. */
/* Two across, then four — never three, so a shelf folded after eight (see
   shelf() in parts.mjs) always ends on a full row. */
.pgrid {
  display: grid; gap: var(--grid-gap);
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (max-width: 639px) { .pgrid { gap: var(--s-3); } }
@media (min-width: 768px) { .pgrid:not(.pgrid--2) { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

.pcard--compact .pcard__media { aspect-ratio: 16 / 9; }
.pcard--compact .pcard__media img { padding: 7% 6%; }
@media (max-width: 639px) { .pcard--compact .pcard__media { aspect-ratio: 2 / 1; } }
/* The stand-in for a missing photograph is sized for a full card. */
.pcard--compact .norec { min-height: 0; padding: var(--s-3); gap: var(--s-2); }
.pcard--compact .norec__note { display: none; }

.pcard--compact .pcard__body {
  padding: var(--s-3);
  /* The buy row lays itself out by the card's own width, not the window's:
     the same card is 150px wide on a phone and 280px on a laptop. */
  container-type: inline-size;
}
@media (min-width: 1024px) { .pcard--compact .pcard__body { padding: var(--s-4); } }

.pcard--compact .pcard__flag { margin-bottom: var(--s-1); }
.pcard__maker {
  display: block;
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
  font-family: var(--font-mono); font-size: var(--fs-label);
  text-transform: uppercase; letter-spacing: .08em; line-height: 1.4;
  color: var(--text-muted);
}
/* Two lines at most. Catalogue titles run to sixty characters. */
.pcard--compact .pcard__name {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden; min-height: 0;
  margin-top: var(--s-1);
  font-size: var(--fs-body-sm); line-height: 1.3;
}
.pcard__buy {
  margin-top: auto; padding-top: var(--s-3);
  display: flex; flex-direction: column; align-items: stretch; gap: var(--s-2);
}
@media (max-width: 639px) { .pcard__buy { padding-top: var(--s-2); } }
.pcard__buy > .addbtn { min-height: 36px; padding-inline: var(--s-2); }
/* The rule above out-ranks the touch-screen .addbtn floor further down, which
   left every shelf's buy button at 36px under a thumb. */
@media (pointer: coarse) { .pcard__buy > .addbtn { min-height: 44px; } }
.pcard--compact .pcard__price { font-size: var(--fs-body); line-height: 1.2; }
@container (min-width: 220px) {
  .pcard__buy { flex-direction: row; align-items: center; justify-content: space-between; }
  .pcard__buy > .addbtn { padding-inline: var(--s-4); }
}
/* The frame lights up with the name's focus, so a keyboard buyer sees which
   card is armed, not just which line of text. */
.pcard--compact:has(.pcard__name:focus-visible) { background: var(--accent); }

/* A long shelf's fold. Opened, the button goes and the cards simply carry on;
   the gap matches the grid so the two halves read as one. */
.shelfmore { margin-top: var(--grid-gap); }
@media (max-width: 639px) { .shelfmore { margin-top: var(--s-3); } }
.shelfmore > summary { list-style: none; }
.shelfmore > summary::-webkit-details-marker { display: none; }
.shelfmore__btn { width: 100%; justify-content: center; min-height: 48px; }
@media (min-width: 640px) { .shelfmore__btn { width: auto; } }
.shelfmore[open] > summary { display: none; }

/* A shelf's heading: name and count on one line, a line of what it is. */
.shelfhead {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--s-1) var(--s-4);
  margin-bottom: var(--s-5);
}
.shelfhead__t { font-size: var(--fs-h3); }
.shelfhead__n {
  font-family: var(--font-mono); font-size: var(--fs-data);
  text-transform: uppercase; letter-spacing: .08em;
  color: var(--text-muted);
}
.shelfhead__d {
  flex: 1 0 100%;
  font-size: var(--fs-body-sm); color: var(--text-secondary);
}
/* A shelf is one band of a catalogue, not an editorial beat: 32px on a phone,
   56px on a laptop. An intro that hands straight on to one does not need its
   160px tail either. */
.section--shelf { padding-block: min(var(--space-block), var(--s-8)); }
.section--intro { padding-bottom: var(--space-block); }

/* --- Category bar ---------------------------------------------------------
   Sticks under the fixed header for the whole page. One row that scrolls
   sideways on a touch screen; the page itself never does. */
.catbar {
  position: sticky; top: var(--header-h); z-index: 90;
  background: rgba(10, 10, 11, .94);
  -webkit-backdrop-filter: blur(12px) saturate(1.1);
          backdrop-filter: blur(12px) saturate(1.1);
  border-block: 1px solid var(--rule-02);
}
.catbar__in {
  position: relative;
  max-width: var(--grid); margin-inline: auto;
  padding: var(--s-2) var(--gutter);
  display: flex; align-items: center; gap: var(--s-2);
  overflow-x: auto; overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.catbar__in::-webkit-scrollbar { display: none; }
.catbar__link {
  --cx: var(--cut-sm-x); --cy: var(--cut-sm-y);
  flex: none;
  display: inline-flex; align-items: center; gap: var(--s-2);
  min-height: 40px; padding: 0 var(--s-3);
  white-space: nowrap;
  font-size: var(--fs-body-sm);
  font-variation-settings: 'wdth' var(--wdth-ui), 'wght' 500;
  color: var(--text);
  background: var(--surface-1);
  box-shadow: inset 0 0 0 1px var(--border-field);
  clip-path: polygon(0 var(--cy), var(--cx) 0, 100% 0,
    100% calc(100% - var(--cy)), calc(100% - var(--cx)) 100%, 0 100%);
  transition: background var(--dur-tint) var(--ease-standard),
              color var(--dur-tint) var(--ease-standard);
}
.catbar__link:hover { background: var(--surface-2); }
.catbar__n {
  font-family: var(--font-mono); font-size: var(--fs-label);
  color: var(--text-muted); font-variant-numeric: tabular-nums;
}
/* The shelf on screen, in three channels: fill, weight and aria-current. */
.catbar__link[aria-current] {
  background: var(--bone); color: var(--ink); box-shadow: none;
  font-variation-settings: 'wdth' var(--wdth-ui), 'wght' 600;
}
.catbar__link[aria-current] .catbar__n { color: var(--ink-muted); }
.catbar__link:focus-visible { outline: none; box-shadow: inset 0 0 0 3px var(--accent); }
@media (pointer: coarse) { .catbar__link { min-height: 44px; } }
/* A mouse cannot swipe, so on a desktop the row wraps instead of hiding. */
@media (hover: hover) and (pointer: fine) {
  .catbar__in { flex-wrap: wrap; overflow-x: visible; }
}
/* Jumping to a shelf lands it below the header and the bar, not under them;
   keyboard focus scrolls by the same padding. */
html:has(.catbar) { scroll-padding-top: calc(var(--header-h) + var(--catbar-h, 64px)); }

/* Wide feature variant: one flagship across the full grid */
.pfeature { position: relative; }
/* The one-of-one shelf on /shop/ renders a feature per product when there are
   fewer than three of them, so the moment the owner marks a second build as
   one-of-one two of these sit directly on top of each other and their 1px
   frames meet as a single seam. The gap is the grid's own, so a pair reads as
   two objects rather than one tall box. */
.pfeature + .pfeature { margin-top: var(--grid-gap); }
.pfeature .framed__in {
  display: grid;
  grid-template-columns: 1.15fr .85fr;
  align-items: center;
}
.pfeature__media {
  position: relative;
  aspect-ratio: 16 / 8;
  display: grid; place-items: center;
  background:
    radial-gradient(110% 130% at 46% 112%, rgba(193, 131, 47, .15) 0%, transparent 60%),
    linear-gradient(180deg, var(--surface-2) 0%, var(--surface-1) 100%);
}
.pfeature__media img {
  width: 92%; height: 86%; object-fit: contain;
  filter: drop-shadow(0 18px 24px rgba(0, 0, 0, .6));
}
.pfeature__body { padding: var(--s-7) var(--s-8); }
@media (max-width: 899px) {
  .pfeature .framed__in { grid-template-columns: 1fr; }
  .pfeature__body { padding: var(--s-6) var(--s-5) var(--s-7); }
}
@media (max-width: 639px) {
  .pfeature__media { aspect-ratio: 16 / 7; }
  .pfeature__body { padding: var(--s-5) var(--s-4); }
}

/* ==========================================================================
   SPEC BLOCK — data as a rifle data card, not a features list.
   The leader does the work of a row border, so there is no striping.
   ========================================================================= */
.spec { display: flex; flex-direction: column; }
.spec__row {
  display: flex; align-items: center; gap: var(--s-3);
  min-height: 28px;
}
.spec__label {
  font-family: var(--font-mono); font-weight: 400;
  font-size: var(--fs-data);
  text-transform: uppercase; letter-spacing: .08em;
  color: var(--text-muted);
  white-space: nowrap;
}
.spec__lead {
  flex: 1; height: 1px; min-width: var(--s-5);
  background: repeating-linear-gradient(to right,
    var(--rule-02) 0 1px, transparent 1px 5px);
}
.spec__val {
  font-family: var(--font-mono); font-weight: 500;
  font-size: var(--fs-data);
  font-variant-numeric: tabular-nums;
  color: var(--text);
  text-align: right;
  white-space: nowrap;
}
.spec__val--accent { color: var(--accent); }
.section--bone .spec__lead {
  background: repeating-linear-gradient(to right,
    var(--bone-line) 0 1px, transparent 1px 5px);
}
/* Wrapping spec rows for long values */
/* --- Wrapping variant --------------------------------------------------
   A real grid, not a wrapping flex row. `display: contents` on each row drops
   the label / leader / value straight into the parent grid, so every label
   shares one left axis and every value shares another. A long value then wraps
   inside its own column instead of dropping below its label and leaving the
   leader running into empty space. */
.spec--wrap {
  display: grid;
  /* The leader is a fixed narrow gutter rather than a greedy 1fr, so the value
     column keeps the room it needs to stay on one line. All values share one
     left axis, so the leader length ends up constant across rows anyway. */
  grid-template-columns: max-content minmax(var(--s-6), 72px) minmax(0, 1fr);
  column-gap: var(--s-3);
  row-gap: var(--s-4);
  align-items: baseline;
}
.spec--wrap .spec__row { display: contents; }
.spec--wrap .spec__val {
  text-align: left;
  white-space: normal;
  text-wrap: pretty;
}
/* Sit the leader on the first line's optical midline. Baseline alignment on a
   1px box is synthesised and ignores margins, so it is offset from the top
   instead: half of the 1.45 line-height, at the data font-size so `em`
   resolves predictably rather than against the inherited body size. */
.spec--wrap .spec__lead {
  align-self: start;
  font-size: var(--fs-data);
  margin-top: .72em;
}

/* A row with an empty label (a continuation line) skips the leader entirely. */
.spec--wrap .spec__row > .spec__label:empty + .spec__lead { background: none; }

/* --- Stacked variant ---------------------------------------------------
   For values that are sentences rather than data. A dotted leader pointing at
   a paragraph is nonsense, so the label sits above and the rows are separated
   by hairlines instead. */
.spec--stack { display: flex; flex-direction: column; }
.spec--stack .spec__row {
  display: block;
  min-height: 0;
  padding-block: var(--s-4);
  border-top: 1px solid var(--rule-02);
}
.spec--stack .spec__row:first-child { border-top: 0; padding-top: 0; }
.spec--stack .spec__row:last-child { padding-bottom: 0; }
.spec--stack .spec__lead { display: none; }
.spec--stack .spec__label { display: block; margin-bottom: var(--s-3); }
.spec--stack .spec__val {
  display: block;
  text-align: left;
  white-space: normal;
  text-wrap: pretty;
  font-family: var(--font);
  font-variation-settings: 'wdth' var(--wdth-text), 'wght' 400;
  font-size: var(--fs-body-sm);
  line-height: 1.55;
  color: var(--text-secondary);
}

.spec-fit { container-type: inline-size; }

/* Below ~440px of its OWN width there is not enough room for label + leader +
   a value on one line, so it stacks. This is a container query rather than a
   media query because the offender is a narrow column inside a wide window. */
@container (max-width: 440px) {
  .spec--wrap { display: flex; flex-direction: column; }
  .spec--wrap .spec__row { display: block; padding-block: var(--s-3); }
  .spec--wrap .spec__lead { display: none; }
  .spec--wrap .spec__label { display: block; margin-bottom: var(--s-2); }
  .spec--wrap .spec__val { display: block; }
}

/* Same fallback keyed to the viewport, for engines without container queries. */
@media (max-width: 599px) {
  .spec--wrap { display: flex; flex-direction: column; }
  .spec--wrap .spec__row { display: block; padding-block: var(--s-3); }
  .spec--wrap .spec__lead { display: none; }
  .spec--wrap .spec__label { display: block; margin-bottom: var(--s-2); }
  .spec--wrap .spec__val { display: block; }
}

/* --- Status: a 6px dot plus the literal word. Colour is never the sole
   carrier of meaning. -------------------------------------------------- */
.status {
  display: inline-flex; align-items: center; gap: var(--s-2);
  font-family: var(--font-mono); font-size: var(--fs-label);
  text-transform: uppercase; letter-spacing: .08em;
  color: var(--text-muted);
}
.status::before {
  content: ""; width: 6px; height: 6px; flex: none;
  background: var(--text-muted);
}
.status--in::before { background: var(--success); }
.status--pre::before { background: var(--warning); }
.status--out::before { background: var(--danger); }

/* ==========================================================================
   OVERSIZED WATERMARK — one per page, maximum.
   Bled off two edges so it reads as architecture, not a background image.
   ========================================================================= */
.watermark {
  position: absolute;
  height: clamp(420px, 62vh, 900px);
  width: auto; aspect-ratio: 395.5 / 690;
  color: var(--bone);
  opacity: .035;
  pointer-events: none;
  z-index: 0;
}
.section--s1 .watermark { opacity: .025; }
.watermark svg { height: 100%; width: 100%; }
.watermark--br { right: -12%; bottom: -18%; }
.watermark--tr { right: -10%; top: -14%; }
.watermark--bl { left: -13%; bottom: -16%; }

/* The watermark is deliberately bled off two edges, so the section has to clip
   it. Without this it widens the document and creates a horizontal scroll. */
.section { isolation: isolate; overflow: hidden; }
.section > .wrap { position: relative; z-index: 1; }

/* --- Section divider: the chevron as a glyph, not an illustration ------- */
.divider {
  display: grid; grid-template-columns: 1fr auto 1fr;
  align-items: center; gap: 20px;
}
.divider::before, .divider::after {
  content: ""; height: 1px; background: var(--rule-01);
}
.divider__glyph { height: 14px; color: var(--text-faint); }
.divider__glyph svg { height: 100%; width: auto; }

/* ==========================================================================
   BLED-TO-BLACK PHOTOGRAPH
   The image has no frame at all — it simply becomes the page.
   ========================================================================= */
.bleed { position: relative; background: var(--bg); overflow: hidden; }
.bleed > img {
  width: 100%; height: 100%; object-fit: cover;
  filter: brightness(.94) contrast(1.08) saturate(.9);
  -webkit-mask-image: radial-gradient(120% 110% at 50% 45%, #000 58%, transparent 100%);
          mask-image: radial-gradient(120% 110% at 50% 45%, #000 58%, transparent 100%);
}
.bleed__scrim {
  position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(to top, rgba(10,10,11,.94) 0%, rgba(10,10,11,.55) 34%, rgba(10,10,11,0) 72%),
    linear-gradient(to right, rgba(10,10,11,.86) 0%, rgba(10,10,11,0) 52%);
}

/* --- The badge as a seal ------------------------------------------------ */
.seal {
  display: flex; align-items: center; gap: var(--s-3);
}
.seal__mark {
  flex: none;
  width: 30px; height: 52px;
  color: var(--rule-03);
}
.seal__mark svg { width: 100%; height: 100%; }
.seal__text {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  text-transform: uppercase;
  letter-spacing: .1em;
  line-height: 1.4;
  color: var(--text-secondary);
}

/* ==========================================================================
   HEADER
   The native lockup ratio cannot work in a header — at 76px the stacked
   wordmark's cap-height would be 6.2px. So the two elements are decoupled
   by a hairline instead of forced into the 3.59 ratio.
   ========================================================================= */
.hdr {
  position: fixed; top: var(--topbar-h); left: 0; right: 0; z-index: 100;
  height: var(--hdr-h);
  display: flex; align-items: center;
  transition: background var(--dur-hover) var(--ease-standard),
              box-shadow var(--dur-hover) var(--ease-standard),
              backdrop-filter var(--dur-hover) var(--ease-standard);
}
.hdr::after {
  content: ""; position: absolute; inset: auto 0 0 0; height: 1px;
  background: var(--rule-02);
  opacity: 0;
  transition: opacity var(--dur-hover) var(--ease-standard);
}
/* Pages that do not open on a dark hero need the header ground from the
   start, or a bone first section renders the bone logo invisible. */
.hdr--solid {
  background: rgba(10, 10, 11, .96);
  -webkit-backdrop-filter: blur(12px) saturate(1.1);
          backdrop-filter: blur(12px) saturate(1.1);
}
.hdr--solid::after { opacity: 1; }

.hdr.is-stuck {
  background: rgba(10, 10, 11, .88);
  -webkit-backdrop-filter: blur(12px) saturate(1.1);
          backdrop-filter: blur(12px) saturate(1.1);
}
.hdr.is-stuck::after { opacity: 1; }

.hdr__in {
  width: 100%; max-width: var(--shell);
  margin-inline: auto; padding-inline: var(--gutter);
  display: flex; align-items: center; gap: var(--s-6);
}

.brand { display: flex; align-items: center; gap: 18px; flex: none; }
.brand__mark { height: 30px; width: auto; color: var(--bone);
  transition: color var(--dur-hover) var(--ease-standard); }
.brand__mark svg { height: 100%; width: auto; }
.brand__div { width: 1px; height: 26px; background: var(--rule-02); flex: none; }
.brand__word { height: 14px; width: auto; color: var(--bone);
  transition: color var(--dur-hover) var(--ease-standard); }
.brand__word svg { height: 100%; width: auto; }
.brand:hover .brand__mark, .brand:hover .brand__word { color: var(--bone-hi); }

.nav { display: flex; align-items: center; gap: var(--s-6); margin-left: auto; }
/* Eight nav items plus a 242px CTA needs 1516px of run; the shell gives 1344 at
   a 1440 laptop, so the CTA was hanging 76px off the right edge on the single
   most common desktop width. Tighten the whole bar across the band where the
   full nav is shown, rather than letting one item overflow. */
@media (max-width: 1679px) {
  .hdr__in { gap: var(--s-4); padding-inline: var(--s-6); }
  .nav { gap: var(--s-4); }
  .nav__link { font-size: var(--fs-label); letter-spacing: .04em; }
  .hdr__cta { margin-left: var(--s-3); }
  .hdr__cta .btn { padding-inline: var(--s-5); }
  .brand { gap: 12px; }
}
@media (max-width: 1439px) {
  .hdr__in { gap: var(--s-3); padding-inline: var(--s-5); }
  .nav { gap: var(--s-3); }
  .nav__link { letter-spacing: .02em; }
  .hdr__cta .btn { padding-inline: var(--s-4); }
}
.nav__link {
  position: relative;
  white-space: nowrap;
  padding-block: var(--s-3);
  font-size: var(--fs-nav);
  font-variation-settings: 'wdth' var(--wdth-ui), 'wght' 500;
  text-transform: uppercase;
  letter-spacing: .06em;
  line-height: 1;
  color: var(--text-secondary);
  transition: color var(--dur-hover) var(--ease-standard);
}
.nav__link:hover { color: var(--text); }
/* Active marker: a 14px accent tick — the truncated chevron apex as UI */
.nav__link::after {
  content: ""; position: absolute; left: 50%; bottom: 0;
  width: 0; height: 2px; background: var(--accent);
  transform: translateX(-50%);
  transition: width var(--dur-move) var(--ease-standard);
}
.nav__link[aria-current="page"] { color: var(--text); }
/* The tool is the one nav item allowed accent treatment. */
.nav__link--accent { color: var(--accent); }
.nav__link--accent:hover { color: var(--accent-hover); }
.nav__link[aria-current="page"]::after { width: 14px; }

.hdr__cta { flex: none; margin-left: var(--s-4); }
.burger {
  display: none; flex: none;
  width: 44px; height: 44px;
  margin-left: var(--s-2);
  place-items: center;
  color: var(--text);
}
.burger span {
  display: block; width: 20px; height: 1.5px; background: currentColor;
  transition: transform var(--dur-move) var(--ease-standard),
              opacity var(--dur-tint) linear;
}
.burger span + span { margin-top: 4.5px; }
.burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

@media (max-width: 1279px) {
  /* A 76px bar with one small mark in it reads as empty. Shorter bar, and the
     wordmark stays — an anonymous chevron is not a header. */
  :root { --hdr-h: 60px; --topbar-h: 30px; }
  .nav, .hdr__cta { display: none; }
  .burger { display: grid; }
  .cartlink { margin-left: auto; margin-right: 0; padding: var(--s-2); }
  .brand { gap: 12px; }
  .brand__mark { height: 26px; }
  .brand__div { height: 22px; }
  .brand__word { height: 12px; }
  /* Hairline before the burger, mirroring the one inside the lockup, so the
     right-hand cluster is structured rather than two icons adrift. */
  .burger::before {
    content: ""; position: absolute; left: -6px;
    width: 1px; height: 22px; background: var(--rule-02);
  }
  .burger { position: relative; margin-left: var(--s-3); }
}
/* Measured: mark + rule + wordmark + both icons + gutters = 285px, so the
   wordmark survives even a 320px viewport. Only drop it below that. */
@media (max-width: 309px) {
  .brand__div, .brand__word { display: none; }
}

/* Mobile drawer */
.drawer {
  position: fixed; inset: var(--header-h) 0 0 0; z-index: 99;
  background: var(--bg);
  padding: var(--s-6) var(--gutter) var(--s-10);
  overflow-y: auto;
  transform: translateX(100%);
  visibility: hidden;
  transition: transform 280ms var(--ease-entrance), visibility 280ms;
}
.drawer.is-open { transform: translateX(0); visibility: visible; }
.drawer__link {
  display: block; padding-block: var(--s-4);
  border-bottom: 1px solid var(--rule-01);
  font-size: var(--fs-h4);
  font-variation-settings: 'wdth' var(--wdth-ui), 'wght' 600;
  text-transform: uppercase; letter-spacing: .04em;
  color: var(--text);
}
.drawer__link[aria-current="page"] { color: var(--accent); }

/* --- Sticky mobile CTA: most traffic arrives when the shop is closed ---- */
.stickycta {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 98;
  display: none;
  padding: var(--s-3) var(--gutter) calc(var(--s-3) + env(safe-area-inset-bottom));
  background: rgba(10, 10, 11, .94);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  border-top: 1px solid var(--rule-02);
}
.stickycta .btn { width: 100%; justify-content: center; }
/* The page ends above the bar, never under it: the bar is 72px plus whatever
   the phone reserves for its home indicator, and the padding counts both. */
@media (max-width: 767px) {
  .stickycta { display: block; }
  /* Held back until the first screen is behind you. A page's own filled button
     sits in its hero; with this bar pinned under it, a phone opened on two
     filled buttons that dial the same number under two different names.
     public/site.js adds .is-down once past ~80% of the window; with scripting
     off the class never arrives and the bar simply stays, as it did before. */
  .js .stickycta {
    transform: translateY(calc(100% + env(safe-area-inset-bottom)));
    transition: transform var(--dur-2, 240ms) var(--ease-standard, ease);
    pointer-events: none;
  }
  .js .stickycta.is-down { transform: none; pointer-events: auto; }
  @media (prefers-reduced-motion: reduce) { .js .stickycta { transition: none; } }
  body { padding-bottom: calc(76px + env(safe-area-inset-bottom)); }
  body.nosticky { padding-bottom: 0; }
  /* ...and a control reached with Tab stops above it too, not behind it. */
  html:has(> body > .stickycta) { scroll-padding-bottom: calc(84px + env(safe-area-inset-bottom)); }
}

/* ==========================================================================
   HERO
   ========================================================================= */
.hero {
  position: relative;
  min-height: min(92vh, 900px);
  display: flex; align-items: flex-end;
  padding-top: calc(var(--header-h) + var(--s-8));
  padding-bottom: var(--space-block);
  overflow: hidden;
  background: var(--bg);
}
.hero__media { position: absolute; inset: 0; z-index: 0; }
.hero__media img {
  width: 100%; height: 100%; object-fit: cover;
  object-position: 62% 42%;
  filter: brightness(.9) contrast(1.08) saturate(.88);
  -webkit-mask-image: radial-gradient(115% 115% at 72% 46%, #000 46%, transparent 100%);
          mask-image: radial-gradient(115% 115% at 72% 46%, #000 46%, transparent 100%);
}
.hero__scrim {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background:
    linear-gradient(to top, rgba(10,10,11,.95) 0%, rgba(10,10,11,.5) 38%, rgba(10,10,11,0) 76%),
    linear-gradient(to right, rgba(10,10,11,.94) 0%, rgba(10,10,11,.72) 34%, rgba(10,10,11,.1) 62%, rgba(10,10,11,0) 78%);
}
.hero__in { position: relative; z-index: 2; width: 100%; }
/* Wide enough that "QUIET DOES / THE WORK." holds its two intended lines
   at the top of the display-1 range. */
.hero__copy { max-width: min(48rem, 66%); }
@media (max-width: 1023px) { .hero__copy { max-width: 100%; } }
.hero__brandline { margin-top: var(--s-5); }
.hero__actions { margin-top: var(--space-block); }

/* The attribute stamp: ACCURATE | QUIET | RESULTS */
.stamp {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--s-3);
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  text-transform: uppercase;
  letter-spacing: .16em;
  color: var(--text-secondary);
}
.stamp__sep { width: 1px; height: 11px; background: var(--rule-03); }
.section--bone .stamp__sep { background: var(--bone-line); }

@media (max-width: 767px) {
  .hero { min-height: 0; padding-top: calc(var(--header-h) + var(--s-7)); }
  .hero__media img { object-position: 50% 38%; }
  .hero__scrim {
    background: linear-gradient(to top, rgba(10,10,11,.96) 0%, rgba(10,10,11,.62) 42%, rgba(10,10,11,.15) 100%);
  }
}
.hero__stamp { margin-top: var(--space-block); max-width: 34rem; }
@media (max-width: 639px) { .hero__stamp { margin-top: var(--s-6); } }

/* --- Shop by category ------------------------------------------------------
   The home page's plain way in: the pages a customer asks for by name, as big
   tappable labels. Two across on a phone, one row on a laptop. */
.shopcats { padding-block: var(--s-6); background: var(--bg); border-top: 1px solid var(--rule-01); }
.shopcats__t {
  font-size: var(--fs-label); letter-spacing: .16em;
  font-variation-settings: 'wdth' 106, 'wght' 600;
  color: var(--text-muted);
  margin-bottom: var(--s-4);
}
.shopcats__grid {
  display: grid; gap: var(--s-2);
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (min-width: 640px) { .shopcats__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .shopcats__grid { grid-template-columns: repeat(6, minmax(0, 1fr)); gap: var(--s-3); } }
.shopcats__link {
  --cx: var(--cut-sm-x); --cy: var(--cut-sm-y);
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-2);
  min-height: 52px; padding: var(--s-2) var(--s-4);
  background: var(--surface-1);
  box-shadow: inset 0 0 0 1px var(--border-field);
  font-size: var(--fs-nav);
  font-variation-settings: 'wdth' var(--wdth-ui), 'wght' 600;
  text-transform: uppercase; letter-spacing: .06em; line-height: 1.2;
  color: var(--text);
  clip-path: polygon(0 var(--cy), var(--cx) 0, 100% 0,
    100% calc(100% - var(--cy)), calc(100% - var(--cx)) 100%, 0 100%);
  transition: background var(--dur-tint) var(--ease-standard), color var(--dur-tint) var(--ease-standard);
}
/* the site's own "this continues" tick */
.shopcats__link::after {
  content: ""; flex: none; width: 14px; height: 2px; background: var(--accent);
  transition: width var(--dur-move) var(--ease-standard);
}
.shopcats__link:hover { background: var(--surface-2); }
.shopcats__link:hover::after { width: 20px; }
.shopcats__link:focus-visible { outline: none; box-shadow: inset 0 0 0 3px var(--accent); }

/* Two example products under each air option: a pair of 350px squares
   stacked on a phone was taller than the option they illustrate. */
.airpair { grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* The home page's Texas standard on a tablet: at half width the "or energy"
   row ran out of its callout. The text takes the wider share; the plate beside
   it is decoration. */
@media (min-width: 640px) and (max-width: 1023px) {
  .txgrid { grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); }
}

/* The trust strip's four facts are two words each: two across on a phone. */
@media (max-width: 639px) {
  .trustgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: var(--s-4); }
}

/* Decoration that earns its place beside text on a wide screen and only adds
   height under it on a phone. Never used for anything that carries meaning. */
@media (max-width: 639px) { .deco-wide { display: none; } }

/* --- Read more ---------------------------------------------------------------
   Long reading folds behind a native <details>: no script, still on the page,
   still indexed. The summary reads as a text action, not a button. */
.foldcopy { margin-top: var(--s-5); }
.foldcopy__sum {
  display: inline-flex; align-items: center; gap: var(--s-2);
  min-height: 44px; cursor: pointer; list-style: none;
  font-size: var(--fs-nav);
  font-variation-settings: 'wdth' var(--wdth-ui), 'wght' 600;
  text-transform: uppercase; letter-spacing: .06em;
  color: var(--accent);
}
.foldcopy__sum::-webkit-details-marker { display: none; }
/* a plus that turns into a minus: a 2px bar, and a second one that folds away */
.foldcopy__sum::after {
  content: ""; flex: none; width: 10px; height: 10px;
  background:
    linear-gradient(currentColor, currentColor) center / 10px 2px no-repeat,
    linear-gradient(currentColor, currentColor) center / 2px 10px no-repeat;
}
.foldcopy[open] > .foldcopy__sum::after {
  background: linear-gradient(currentColor, currentColor) center / 10px 2px no-repeat;
}
.foldcopy__sum:hover { color: var(--accent-hover); }
.foldcopy__body { padding-top: var(--s-2); }
.foldcopy--card { margin-top: var(--s-2); }

/* Reasons as a list of one-line answers, each opening onto its paragraph. */
.pillars { column-gap: var(--s-10); row-gap: 0; }
.pillar { margin-top: 0; border-top: 1px solid var(--rule-02); }
.pillar__sum {
  display: flex; align-items: center; gap: var(--s-3);
  min-height: 56px; padding-block: var(--s-3);
  cursor: pointer; list-style: none;
}
.pillar__sum::-webkit-details-marker { display: none; }
.pillar__n { flex: none; color: var(--accent); }
.pillar__t {
  flex: 1;
  font-size: var(--fs-h4);
  font-variation-settings: 'wdth' var(--wdth-text), 'wght' 600;
  line-height: 1.3;
}
.pillar__sum::after {
  content: ""; flex: none; width: 12px; height: 12px; color: var(--accent);
  background:
    linear-gradient(currentColor, currentColor) center / 12px 2px no-repeat,
    linear-gradient(currentColor, currentColor) center / 2px 12px no-repeat;
}
.pillar[open] > .pillar__sum::after {
  background: linear-gradient(currentColor, currentColor) center / 12px 2px no-repeat;
}
.pillar__sum:hover .pillar__t { color: var(--accent); }
.pillar > .foldcopy__body { padding: 0 0 var(--s-5); }

/* Events on the home page: when, what, what it costs, with the detail folded. */
.events {
  list-style: none;
  display: grid; gap: 0 var(--grid-gap);
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.events__row {
  display: flex; flex-direction: column; align-items: flex-start;
  padding-top: var(--s-4);
  border-top: 1px solid var(--rule-02);
}
.events__name { margin-top: var(--s-2); font-size: var(--fs-h4); }
.events__cost {
  margin-top: var(--s-2);
  font-family: var(--font-mono); font-size: var(--fs-label);
  text-transform: uppercase; letter-spacing: .08em;
  color: var(--text-muted);
}
@media (max-width: 899px) {
  .events { grid-template-columns: minmax(0, 1fr); }
  .events__row { padding-block: var(--s-3) var(--s-1); }
}

/* ==========================================================================
   MISSION CARD — self-selection by problem, not product type
   ========================================================================= */
.mission { display: block; height: 100%; }
.mission .framed__in { display: flex; flex-direction: column; }
.mission__media {
  display: block;
  position: relative; aspect-ratio: 16 / 9; overflow: hidden;
  background: linear-gradient(178deg, var(--bone-hi) 0%, var(--bone) 46%, var(--bone-lo) 100%);
}
.mission__media img {
  width: 100%; height: 100%; object-fit: cover;
  transition: transform var(--dur-plate) var(--ease-standard);
}
.mission__media--plate img {
  width: 88%; height: 84%; margin: 8% 6%;
  object-fit: contain;
  mix-blend-mode: multiply;
  filter: contrast(1.04) saturate(.96);
}
.mission:hover .mission__media img { transform: scale(1.028); }
.mission__body { flex: 1; display: flex; flex-direction: column; padding: var(--s-6); }
.mission__body > .rule { margin-block: var(--s-4); }
.mission__title { display: block; }
.mission__text { display: block; margin-top: var(--s-4); }
.mission__quarry {
  margin-top: auto; padding-top: var(--s-5);
  font-family: var(--font-mono); font-size: var(--fs-label);
  text-transform: uppercase; letter-spacing: .08em;
  color: var(--text-muted);
}
/* On a phone the four doors sit two by two and say only what they open. */
@media (max-width: 639px) {
  .missiongrid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-3); }
  .missiongrid .mission__body { padding: var(--s-3); }
  .missiongrid .mission__body > .rule,
  .missiongrid .mission__text,
  .missiongrid .mission__quarry { display: none; }
  .missiongrid .eyebrow { letter-spacing: .1em; line-height: 1.3; }
  .missiongrid .mission__title { margin-top: var(--s-2); font-size: var(--fs-h4); }
}

/* --- Generic content card ---------------------------------------------- */
.card { display: flex; flex-direction: column; height: 100%; }
.card .framed__in { display: flex; flex-direction: column; height: 100%; padding: var(--s-6); }
.card--pad-lg .framed__in { padding: var(--s-7); }
@media (max-width: 639px) {
  .card .framed__in, .card--pad-lg .framed__in { padding: var(--s-5); }
}

/* --- Numbered list: chapter numbers in mono ---------------------------- */
.numbered { counter-reset: n; }
.numbered__item { counter-increment: n; position: relative; padding-left: var(--s-8); }
.numbered__item::before {
  content: "0" counter(n);
  position: absolute; left: 0; top: .1em;
  font-family: var(--font-mono); font-size: var(--fs-data);
  color: var(--accent); letter-spacing: .06em;
}

/* --- Brand wall -------------------------------------------------------- */
.brands { display: flex; flex-wrap: wrap; gap: var(--s-2) 0; }
.brands__item {
  display: inline-flex; align-items: center;
  padding: var(--s-2) var(--s-5) var(--s-2) 0;
  margin-right: var(--s-5);
  font-size: var(--fs-body-sm);
  font-variation-settings: 'wdth' var(--wdth-ui), 'wght' 500;
  color: var(--text-secondary);
  letter-spacing: .01em;
  border-right: 1px solid var(--rule-01);
}
.brands__item:last-child { border-right: 0; }
@media (max-width: 639px) {
  .brands { gap: 0; }
  .brands__item { padding: var(--s-1) var(--s-3) var(--s-1) 0; margin-right: var(--s-3); }
}

/* --- Hours table ------------------------------------------------------- */
.hours { width: 100%; border-collapse: collapse; }
.hours td {
  padding: var(--s-3) 0;
  font-size: var(--fs-body-sm);
  border-bottom: 1px solid var(--rule-02);
  vertical-align: baseline;
}
@media (max-width: 639px) { .hours td { padding-block: var(--s-2); } }
.hours tr:last-child td { border-bottom: 0; }
.hours td:first-child { color: var(--text-muted); }
.hours td:last-child {
  text-align: right; font-family: var(--font-mono);
  font-size: var(--fs-data); font-variant-numeric: tabular-nums;
}
/* The hours table is always inside a .framed card, so its ground is
   --surface-1 and --text-faint fell to 4.28:1 on the rows that say when the
   shop is shut — the rows a customer most needs to read before driving out. */
.hours .is-closed td { color: var(--text-faint-on-s2); }
.hours .is-today td:first-child { color: var(--accent); }
.hours .is-today td:last-child { color: var(--accent); }

/* --- Callout ----------------------------------------------------------- */
.callout {
  padding: var(--s-6);
  background: var(--accent-wash);
  box-shadow: inset 2px 0 0 var(--accent);
}
.callout--bone { background: rgba(126, 79, 20, .07); box-shadow: inset 2px 0 0 var(--ink-accent); }

/* --- Footer ------------------------------------------------------------ */
.ftr { background: var(--surface-sunk); padding-block: var(--space-block) var(--s-7); }
.ftr__top {
  display: grid; grid-template-columns: minmax(240px, 1fr) 2.4fr;
  gap: var(--space-block) var(--grid-gap);
  padding-bottom: var(--space-block);
}
/* Inline elements ignore height — the lockup lives in a plain div, unlike the
   header/seal marks which are flex children and get blockified. */
.ftr__lockup { display: block; height: 88px; width: fit-content; color: var(--bone); }
.ftr__lockup svg { height: 100%; width: auto; }
.ftr__meta {
  margin-top: var(--s-6);
  font-family: var(--font-mono); font-size: var(--fs-label);
  text-transform: uppercase; letter-spacing: .16em;
  color: var(--text-muted); line-height: 1.7;
}
.ftr__cols { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--grid-gap); }
.ftr__col h2 {
  font-size: var(--fs-label);
  font-variation-settings: 'wdth' 106, 'wght' 600;
  text-transform: uppercase; letter-spacing: .16em;
  color: var(--text); margin-bottom: var(--s-4);
}
.ftr__col a {
  display: block; padding-block: var(--s-2);
  font-size: var(--fs-body-sm); color: var(--text-muted);
  transition: color var(--dur-hover) var(--ease-standard);
}
.ftr__col a:hover { color: var(--text); }
.ftr__bottom {
  padding-top: var(--s-6);
  display: flex; flex-wrap: wrap; gap: var(--s-4) var(--s-6);
  justify-content: space-between; align-items: center;
  font-size: var(--fs-label); color: var(--text-faint);
  font-family: var(--font-mono); letter-spacing: .08em; text-transform: uppercase;
}
/* --------------------------------------------------------------------------
   Who built it.

   Below the shop's own legal line and on its own rule, centred, with air above
   and below it — the credit is not a fourth item in the small print, and set
   inline with the copyright it read as one. The label is the small-caps the
   site uses for every eyebrow; the name sits at reading size in the text colour
   so the eye lands on it, and picks up the accent on hover like any other link
   worth following.
   -------------------------------------------------------------------------- */
.ftr__by {
  margin-top: var(--s-6);
  padding-top: var(--s-6);
  border-top: 1px solid var(--rule-01);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--s-1) var(--s-4);
  text-align: center;
}
.ftr__by-eyebrow {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.ftr__by-name {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-inline: var(--s-1);
  font-size: var(--fs-body);
  letter-spacing: .02em;
  font-variation-settings: 'wdth' var(--wdth-text), 'wght' 600;
  color: var(--text);
  text-decoration: none;
  border-bottom: 1px solid var(--rule-02);
}
.ftr__by-name:hover,
.ftr__by-name:focus-visible { color: var(--accent); border-bottom-color: var(--accent); }

@media (max-width: 899px) {
  .ftr__top { grid-template-columns: 1fr; }
  .ftr__cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* Two columns stay two on a phone. The labels are short now, and one column of
   eighteen 44px links was a screen and a half of footer on every page. */
@media (max-width: 639px) {
  .ftr__cols { gap: var(--s-5) var(--s-4); }
  .ftr__lockup { height: 64px; }
  .ftr__meta { margin-top: var(--s-4); }
  .ftr__col h2 { margin-bottom: var(--s-2); }
  .ftr__bottom { padding-top: var(--s-5); gap: var(--s-2) var(--s-5); }
}

/* ==========================================================================
   MOTION
   ========================================================================= */
/* Fails OPEN. Visible by default; only opts into the hidden start state when
   scripting is available, so a failed or slow IntersectionObserver can never
   leave a viewport of empty near-black.

   `:not(.is-in)` rather than a competing `.is-in` rule: once the class lands
   the hidden rule stops matching altogether, so there is no specificity fight
   to lose. */
.reveal { opacity: 1; transform: none; }
html.js .reveal { transition: opacity var(--dur-reveal) var(--ease-entrance),
                              transform var(--dur-reveal) var(--ease-entrance); }
html.js .reveal:not(.is-in) { opacity: 0; transform: translateY(14px); }

.rule--wipe { transform: none; }
html.js .rule--wipe { transition: transform var(--dur-wipe) var(--ease-mechanical); }
html.js .rule--wipe:not(.is-in) { transform: scaleX(0); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  html { scroll-behavior: auto; }
  .reveal, .reveal.is-in { opacity: 1 !important; transform: none !important; }
  .rule--wipe, .rule--wipe.is-in { transform: none !important; }
  /* Colour feedback is an affordance, not decoration — keep it. */
  .btn, .nav__link, .tlink, .ftr__col a, .prose a {
    transition: background 120ms linear, color 120ms linear !important;
  }
}

/* --- Print ------------------------------------------------------------- */
@media print {
  .hdr, .drawer, .stickycta, .watermark, .hero__media { display: none !important; }
  body { background: #fff; color: #000; }
}

/* ==========================================================================
   CART & CHECKOUT
   ========================================================================= */
.cartlink {
  position: relative; flex: none;
  display: inline-flex; align-items: center; gap: var(--s-2);
  padding: var(--s-3);
  color: var(--text-secondary);
  transition: color var(--dur-hover) var(--ease-standard);
}
.cartlink:hover { color: var(--text); }
.cartlink svg { width: 19px; height: 19px; }
.cartlink__n {
  min-width: 17px; height: 17px; padding: 0 4px;
  display: grid; place-items: center;
  background: var(--accent); color: var(--accent-ink);
  font-family: var(--font-mono); font-size: 10px; line-height: 1;
  font-variant-numeric: tabular-nums;
}
.cartlink__n[hidden] { display: none; }

/* Add-to-cart sits in the card footer beside the price */
.addbtn {
  --cx: var(--cut-sm-x); --cy: var(--cut-sm-y);
  display: inline-flex; align-items: center; justify-content: center;
  padding: var(--s-2) var(--s-4);
  background: var(--surface-2);
  box-shadow: inset 0 0 0 1px var(--border-field);
  color: var(--text);
  font-size: var(--fs-label);
  font-variation-settings: 'wdth' var(--wdth-ui), 'wght' 600;
  text-transform: uppercase; letter-spacing: .06em; line-height: 1;
  white-space: nowrap;
  clip-path: polygon(0 var(--cy), var(--cx) 0, 100% 0,
    100% calc(100% - var(--cy)), calc(100% - var(--cx)) 100%, 0 100%);
  transition: background var(--dur-tint) var(--ease-standard),
              color var(--dur-tint) var(--ease-standard);
}
.addbtn:hover { background: var(--accent); color: var(--accent-ink); box-shadow: none; }

/* On a touchscreen these have to be tappable, not merely visible.
   Measured on a 375px viewport: the Add button on a cross-sell card was 27px
   tall and the cart link was 43px — both under the 44px floor, and the first of
   them is a buy button. Scoped to coarse pointers so the desktop composition,
   which was signed off, is untouched. */
@media (pointer: coarse) {
  .addbtn { min-height: 44px; }
  .cartlink { min-width: 44px; min-height: 44px; }

  /* Measured on a real 375px viewport, not assumed. Everything below was
     between 24px and 39px tall — usable with a mouse, fiddly with a thumb.
     Scoped to coarse pointers so the desktop composition, which is signed off,
     is not touched by any of it.

     A .tlink inside running prose is deliberately NOT included: it is a word in
     a sentence, and padding it out would wreck the line spacing of the
     paragraph it sits in. Only the ones standing on their own as an action are
     raised. */
  .brand { min-height: 44px; }
  .topbar__in { min-height: 44px; }

  /* The footer's link columns: 39 pixels, which is close enough to look right
     and short enough to miss. */
  .ftr__col a { min-height: 44px; display: flex; align-items: center; }

  /* "Send a message", "Call 830-375-3584", "Email the shop" — the shop's
     primary contact actions, standing alone in the footer at 24px. */
  .stack > .tlink,
  .row > .tlink,
  .pcard__actions .tlink { min-height: 44px; }

  /* A card's title is a second route to a product that the photograph and the
     Details link already offer, so it is left as text rather than padded into
     a block that would push every card taller. */
}

.addbtn.is-added { background: var(--success); color: var(--accent-ink); box-shadow: none; }

/* Product card footer becomes a two-row block: price/vendor then actions */
.pcard__actions {
  display: flex; align-items: center; gap: var(--s-3);
  margin-top: var(--s-4); padding-top: var(--s-4);
  border-top: 1px solid var(--rule-01);
}
.pcard__actions .tlink { font-size: var(--fs-label); margin-left: auto; }

/* --- Cart lines --------------------------------------------------------- */
.cline {
  display: grid;
  grid-template-columns: 120px 1fr auto;
  gap: var(--s-5);
  align-items: center;
  padding-block: var(--s-5);
  border-bottom: 1px solid var(--rule-01);
}
.cline__media {
  aspect-ratio: 1; display: grid; place-items: center; overflow: hidden;
  background:
    radial-gradient(120% 140% at 50% 118%, rgba(193,131,47,.12) 0%, transparent 62%),
    linear-gradient(180deg, var(--surface-2) 0%, var(--surface-1) 100%);
}
.cline__media img { width: 86%; height: 86%; object-fit: contain; }
.cline__name {
  font-size: var(--fs-h4);
  font-variation-settings: 'wdth' var(--wdth-text), 'wght' 600;
  line-height: 1.25;
}
.cline__unit { margin-top: var(--s-2); }
.cline__ctl { display: flex; align-items: center; gap: var(--s-2); margin-top: var(--s-4); }
.qty {
  width: 30px; height: 30px; display: grid; place-items: center;
  box-shadow: inset 0 0 0 1px var(--border-field);
  color: var(--text); font-size: var(--fs-body);
  transition: background var(--dur-tint) var(--ease-standard);
}
.qty:hover { background: var(--surface-2); }
.qty__n {
  min-width: 32px; text-align: center;
  font-family: var(--font-mono); font-size: var(--fs-data);
  font-variant-numeric: tabular-nums;
}
.cline__rm {
  margin-left: var(--s-4);
  font-family: var(--font-mono); font-size: var(--fs-label);
  text-transform: uppercase; letter-spacing: .08em;
  color: var(--text-muted);
  transition: color var(--dur-hover) var(--ease-standard);
}
.cline__rm:hover { color: var(--danger); }
.cline__amt {
  font-size: var(--fs-body-lg);
  font-variation-settings: 'wdth' var(--wdth-ui), 'wght' 600;
  font-variant-numeric: tabular-nums;
  text-align: right;
}
@media (max-width: 639px) {
  .cline { grid-template-columns: 84px 1fr; }
  .cline__amt { grid-column: 2; text-align: left; margin-top: var(--s-3); }
}

.checkerr {
  margin-top: var(--s-5); padding: var(--s-4);
  background: rgba(217, 85, 60, .12);
  box-shadow: inset 2px 0 0 var(--danger);
  font-size: var(--fs-body-sm);
  color: var(--text);
}
.agree {
  display: flex; gap: var(--s-3); align-items: flex-start;
  margin-top: var(--s-6);
  font-size: var(--fs-body-sm); color: var(--text-secondary);
  cursor: pointer;
}
.agree input {
  flex: none; width: 18px; height: 18px; margin-top: 2px;
  accent-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--border-field);
}

/* min-width:0 on both tracks: without it a nowrap spec label sets a
   min-content floor and the whole column inflates past the viewport. */
.cart-grid { grid-template-columns: minmax(0, 1.55fr) minmax(0, .95fr); gap: var(--space-block) var(--s-10); }
.cart-grid > * { min-width: 0; }
@media (max-width: 959px) { .cart-grid { grid-template-columns: 1fr; } }

/* ── Contrast guard ───────────────────────────────────────────────────────
   --accent (#C1832F) measures 2.64:1 on --bone (#EDE9E0) and fails WCAG AA.
   --ink-accent (#7E4F14) measures 5.75:1 and passes. The .section--bone token
   remap in 02-base.css already turns var(--accent) into --ink-accent, inline
   styles included, so no rule is needed here. Two selectors that once stood
   here had lost their declaration and were silently joined onto the
   .pest-split rule below, which made every inline-styled label on bone a
   two-column grid: "Deer, bighorn sheep…" on the home page wrapped into half
   its width. */

/* ── Fixes from the QA audit ─────────────────────────────────────────────── */

/* .pest-split carried an inline two-column grid and no stylesheet rule, so it
   never collapsed: at 390px the first track resolved to 437px inside a 348px
   box and the image track collapsed to zero. */
.pest-split { display: grid; grid-template-columns: 1.1fr .9fr; gap: 0; align-items: stretch; }
@media (max-width: 899px) { .pest-split { grid-template-columns: 1fr; } }
.pest-split__copy { padding: var(--space-block) var(--s-8); }
.pest-split__img { min-height: 340px; }
@media (max-width: 899px) {
  .pest-split__copy { padding: var(--s-6) var(--s-5); }
  .pest-split__img { min-height: 0; aspect-ratio: 16 / 9; }
}

/* The map wrapper had min-height + aspect-ratio and no width constraint, which
   resolved its width to 731px on every phone. */
.mapframe { width: 100%; max-width: 100%; aspect-ratio: 16 / 9; min-height: 0; }
@media (min-width: 768px) { .mapframe { aspect-ratio: 16 / 7; min-height: 320px; } }

/* --text-faint now passes AA on both grounds, so these no longer need to
   opt out of it. .ftr__bottom keeps --text-muted for hierarchy, not rescue. */
.ftr__bottom { color: var(--text-muted); }

/* Long spec values on the Texas page overflowed at 320px. */
@media (max-width: 379px) {
  .spec__val, .spec__label { white-space: normal; }
}

/* N10 · touch targets. WCAG 2.2 SC 2.5.8 wants 24x24 minimum; these were
   measured at 18px tall. Padding rather than height so nothing reflows. */
.cline__rm { padding-block: var(--s-2); min-height: 24px; display: inline-flex; align-items: center; }
.agree { padding-block: var(--s-2); }
.agree input { width: 24px; height: 24px; }
.tlink { min-height: 24px; }
.ftr__bottom a { display: inline-flex; align-items: center; min-height: 24px; }
.qty { width: 36px; height: 36px; }
/* the card title is the primary tap target on a product card and measured
   23px - one pixel short. inline-flex so the box grows, not the line. */
.pcard__name { display: inline-flex; align-items: center; min-height: 24px; }

/* A square plate variant, so nothing ever inlines aspect-ratio again. */
.plate--sq { aspect-ratio: 1; }

/* --- Air & charging list ------------------------------------------------
   Was three dark cards dropped into a bone section: the names went dark-on-dark
   and the whole block read as a foreign object. On bone, the list is the
   component — hairlines carry the structure, the plate carries the product. */
.airlist { display: block; border-top: 1px solid var(--rule-01); }
.airrow {
  display: grid; align-items: center; gap: 0 var(--s-5);
  grid-template-columns: 2.25rem 88px minmax(0, 1fr) auto;
  padding-block: var(--s-5);
  border-bottom: 1px solid var(--rule-01);
  color: inherit; text-decoration: none;
  transition: background var(--dur-hover) var(--ease-standard), padding-inline var(--dur-hover) var(--ease-standard);
}
.airrow__n { color: var(--text-faint); font-variant-numeric: tabular-nums; }
.airrow__media { width: 88px; }
.airrow__body { display: block; min-width: 0; }
.airrow__name { display: block; }
.airrow__kicker { display: block; margin-top: var(--s-2); color: var(--text-muted); }
.airrow__price { color: var(--accent); font-size: var(--fs-body-lg); white-space: nowrap; }

@media (hover: hover) {
  .airrow:hover { background: var(--accent-wash); padding-inline: var(--s-4); }
  .airrow:hover .airrow__n { color: var(--accent); }
}
.airrow:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

@media (max-width: 560px) {
  .airrow { grid-template-columns: 64px minmax(0, 1fr); gap: var(--s-2) var(--s-4); }
  .airrow__n { display: none; }
  .airrow__media { width: 64px; }
  .airrow__price { grid-column: 2; justify-self: start; }
}

/* --- No photograph on record -------------------------------------------
   A broken-image icon on a page arguing "these are not toys" is the single
   cheapest-looking thing a site can do. State the gap instead. */
.norec {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--s-3); width: 100%; height: 100%; min-height: 8rem;
  padding: var(--s-6); text-align: center;
  background:
    linear-gradient(180deg, var(--surface-2), var(--surface-1)),
    var(--surface-1);
  box-shadow: inset 0 0 0 1px var(--rule-02);
}
.norec__mark {
  width: 26px; height: 26px; opacity: .5;
  background: currentColor;
  -webkit-mask: url('/img/brand/mark.svg?v=84433506') center / contain no-repeat;
          mask: url('/img/brand/mark.svg?v=84433506') center / contain no-repeat;
}
.norec__label { color: var(--text-muted); }
/* The stand-in paints its own --surface-2 to --surface-1 gradient, so on the
   dark palette this line never sits on --bg: it was reading between 3.96:1 and
   4.28:1. Inside a bone section both the gradient and the token follow the
   remap, so it was already fine there. */
.norec__note { color: var(--text-faint-on-s2); max-width: 22ch; line-height: 1.35; }

/* "From" on a range price — smaller and quieter than the figure, because the
   figure is what the eye is looking for. */
.price__from {
  font-family: var(--font-mono); font-size: .72em;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-muted); margin-right: .35em;
}

/* No confirmed price yet. It sits where the figure sits and reads at the same
   weight as "From", so a card with one does not look broken — it looks like a
   shop that will not print a number it has not agreed. */
.price__poa {
  font-family: var(--font-mono); font-size: .68em;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-muted);
}

/* --- Top bar -------------------------------------------------------------
   Built as a SPEC ROW, not a banner: mono label, dotted leader, value — the
   same device the rifle and selector pages use. A promo strip in a different
   visual language is the fastest way to make a site look assembled from parts.
   Sunk surface so it reads as beneath the brand, not competing with it. */
.topbar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 101;
  height: var(--topbar-h);
  display: flex; align-items: stretch;
  background: var(--surface-sunk);
  border-bottom: 1px solid var(--rule-01);
}
.topbar__in {
  width: 100%; max-width: var(--shell);
  margin-inline: auto; padding-inline: var(--gutter);
  display: flex; align-items: center; gap: var(--s-4);
  min-width: 0;
  color: var(--text-muted); text-decoration: none;
  transition: color var(--dur-hover) var(--ease-standard);
}
.topbar__k { color: var(--text-secondary); flex: none; }
/* the dotted leader, exactly as the spec block draws it */
.topbar__lead {
  flex: 1 1 auto; min-width: var(--s-6);
  height: 1px; align-self: center;
  background-image: radial-gradient(circle, var(--rule-02) 1px, transparent 1px);
  background-size: 5px 1px; background-repeat: repeat-x;
  background-position: left center;
}
.topbar__v {
  flex: 0 1 auto; min-width: 0;
  font-size: var(--fs-body-sm); color: var(--text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* not a button — a terminated accent tick, the site's own "this continues" mark */
.topbar__cta {
  flex: none; position: relative;
  padding-right: 20px; min-height: 24px;
  display: inline-flex; align-items: center;
  color: var(--accent);
  transition: color var(--dur-hover) var(--ease-standard);
}
.topbar__cta::after {
  content: ""; position: absolute; right: 0; top: 50%;
  width: 14px; height: 2px; margin-top: -1px;
  background: var(--accent);
  transition: width var(--dur-move) var(--ease-standard);
}
.topbar__in:hover { color: var(--text-secondary); }
.topbar__in:hover .topbar__cta { color: var(--accent-hover); }
.topbar__in:hover .topbar__cta::after { width: 20px; }
.topbar__in:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

@media (max-width: 1023px) { .topbar__v { display: none; } }
@media (max-width: 719px) {
  .topbar__in { padding-inline: var(--s-5); gap: var(--s-3); }
}

/* ============================================================================
   FIND MY RIFLE
   Assembled entirely from devices the system already owns — the 3:8 cut, the
   spec block, terminated hairlines. No new colour, cut ratio, easing or
   duration is introduced.

   Surface logic follows the system rule: questions are TEACHING, so they sit
   on bone; results are PRODUCT THEATRE, so they sit on dark.
   ========================================================================= */

.sel { display: grid; grid-template-columns: 7fr 5fr; gap: var(--s-10); align-items: start; }
@media (max-width: 899px) { .sel { grid-template-columns: 1fr; gap: var(--space-block); } }

.sel__head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-4); margin-bottom: var(--s-4); }
.sel__back {
  font-family: var(--font-mono); font-size: var(--fs-label);
  text-transform: uppercase; letter-spacing: .08em; color: var(--ink-muted);
  padding: var(--s-2) 0; min-height: 44px;
}
.sel__back:disabled { opacity: .35; cursor: default; }
.sel__head .data-label { color: var(--ink-muted); }

.sel__q { margin-top: var(--s-6); font-size: var(--fs-h2); color: var(--ink); }
.sel__q:focus { outline: none; }
.sel__q:focus-visible { outline: 2px solid var(--ink-accent); outline-offset: 4px; }
.sel__help { margin-top: var(--s-4); font-size: var(--fs-body); color: var(--ink-muted); max-width: 56ch; }

.sel__opts { border: 0; margin-top: var(--space-block); display: flex; flex-direction: column; gap: var(--s-3); }

/* Option row — the 3:8 cut at small scale, same as .btn */
.opt {
  --cx: var(--cut-sm-x); --cy: var(--cut-sm-y);
  position: relative; display: flex; align-items: center; gap: var(--s-4);
  min-height: 56px; padding: var(--s-4) var(--s-5);
  background: var(--bone-hi); color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--bone-line);
  cursor: pointer;
  clip-path: polygon(0 var(--cy), var(--cx) 0, 100% 0,
                     100% calc(100% - var(--cy)), calc(100% - var(--cx)) 100%, 0 100%);
  transition: background var(--dur-tint) var(--ease-standard);
}
.opt:hover { background: var(--bone); }
.opt input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.opt__body { flex: 1; display: flex; flex-direction: column; gap: 2px; }
.opt__l { font-variation-settings: 'wdth' var(--wdth-ui), 'wght' 500; font-size: var(--fs-body); }
.opt__s { font-family: var(--font-mono); font-size: var(--fs-label); color: var(--ink-muted); }
.opt__tick { font-family: var(--font-mono); opacity: 0; flex: none; }

/* Selected state uses THREE channels — fill, edge bar, glyph — never colour alone. */
.opt:has(input:checked) { background: var(--ink); color: var(--bone); box-shadow: inset 3px 0 0 var(--accent); }
.opt:has(input:checked) .opt__s { color: var(--bone-lo); }
.opt:has(input:checked) .opt__tick { opacity: 1; }
.opt:has(input:focus-visible) { outline: 2px solid var(--ink-accent); outline-offset: 2px; }
.opt--unsure { background: transparent; color: var(--ink-muted); }
.opt--unsure:hover { background: var(--bone); color: var(--ink); }

.sel__next { margin-top: var(--space-block); }

/* --- Job sheet: the progress indicator IS the answer set, and every filled
       row is a button back to that question. One component, four jobs. -----
   Named .jobsheet, not .js: layout.mjs marks <html> with the class "js" on
   every page, so a sticky .js rule made the whole document sticky — short
   pages drew 134px low on wide screens. */
.jobsheet { position: sticky; top: calc(var(--header-h) + var(--s-5)); }
@media (max-width: 899px) { .jobsheet { position: static; } }
.jobsheet__spec { margin-top: var(--s-5); }
.jobsheet__row {
  display: grid; grid-template-columns: max-content minmax(var(--s-6), 72px) minmax(0, 1fr);
  column-gap: var(--s-3); align-items: baseline; width: 100%; text-align: left;
  padding-block: var(--s-3); min-height: 44px;
}
button.jobsheet__row { cursor: pointer; }
button.jobsheet__row:hover .spec__val { color: var(--ink-accent); }
.jobsheet__row .spec__lead { align-self: start; margin-top: .72em; font-size: var(--fs-data);
  background: repeating-linear-gradient(to right, var(--bone-line) 0 1px, transparent 1px 5px); }
.jobsheet__row .spec__lead.is-current { background: repeating-linear-gradient(to right, var(--ink-accent) 0 1px, transparent 1px 5px); }
.jobsheet__row .spec__label { color: var(--ink-muted); }
.jobsheet__row .spec__val { color: var(--ink); text-align: left; }
.jobsheet__count { margin-top: var(--s-5); color: var(--ink-muted); }

/* --- Results: the ground goes dark. The lights going down on three rifles is
       the emotional beat of the tool, and it costs nothing to build. -------- */
.sel--res {
  display: block; background: var(--bg); color: var(--text);
  /* full-bleed to the viewport, like every other band on the site, rather than
     a 1296px slab with bone showing either side */
  margin-inline: calc(50% - 50vw);
  padding: var(--space-block) max(var(--gutter), calc(50vw - var(--grid) / 2 + var(--gutter))) var(--space-section);
}
.sel__resulthead h2 { margin-top: var(--s-6); color: var(--text); }
.sel__resulthead h2:focus { outline: none; }
.sel__resulthead h2:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }

.sel__cards { list-style: none; display: grid; gap: var(--grid-gap); margin-top: var(--space-block);
  grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 1023px) { .sel__cards { grid-template-columns: 1fr; } }

.rescard .framed__in { display: flex; flex-direction: column; height: 100%; }
.rescard__media { display: grid; place-items: center; aspect-ratio: 16/7; overflow: hidden;
  background: radial-gradient(120% 140% at 50% 118%, rgba(193,131,47,.13) 0%, transparent 62%),
              linear-gradient(180deg, var(--surface-2) 0%, var(--surface-1) 100%); }
.rescard__media img { width: 88%; height: 84%; object-fit: contain;
  filter: drop-shadow(0 14px 18px rgba(0,0,0,.55)); transition: transform var(--dur-plate) var(--ease-standard); }
.rescard:hover .rescard__media img { transform: scale(1.028); }
.rescard__body { flex: 1; display: flex; flex-direction: column; padding: var(--s-5); }
.rescard__body h3 { margin-top: var(--s-3); font-size: var(--fs-h3); }
.rescard__body h3 a { transition: color var(--dur-hover) var(--ease-standard); }
.rescard__body h3 a:hover { color: var(--accent); }
.rescard__body .eyebrow { margin-top: var(--s-4); }
/* .framed is a 1px outer layer; colour THAT. An outer box-shadow on a
   clip-path'd element is clipped away and never paints. */
.rescard--top { background: var(--accent); }
.rescard--top .rescard__body > .rule { background: var(--accent); }
.rescard__foot { margin-top: auto; padding-top: var(--s-5); display: flex;
  align-items: baseline; justify-content: space-between; gap: var(--s-3);
  border-top: 1px solid var(--rule-01); }
.rescard__cta { margin-top: var(--s-5); justify-content: center; }

.sel__assumed, .sel__cant { margin-top: var(--space-section); max-width: 60rem; }
.sel__cant .spec--stack .spec__val { color: var(--text-secondary); }
.linkish { color: var(--accent); font: inherit; text-decoration: underline;
  text-underline-offset: 3px; cursor: pointer; }
.sel__actions { margin-top: var(--space-section); display: flex; flex-wrap: wrap;
  align-items: center; gap: var(--s-4); }

/* The page-level sticky phone bar must not stack with the selector's own
   Continue button — two stacked bars eat 112px of an 812px viewport. */
@media (max-width: 767px) {
  body:has(#selector) .stickycta { display: none; }
  body:has(#selector) { padding-bottom: 0; }
}

/* ── Compact intro ────────────────────────────────────────────────────────
   A full display-2 hero pushed the first question 788px down the page, so the
   tool opened below the fold and every answer looked like a jump. The intro is
   now a two-column band that leaves the question visible on arrival. */
.sel__intro { padding-block: calc(var(--header-h) + var(--s-7)) var(--s-7); }
.sel__introgrid {
  display: grid; grid-template-columns: 1.15fr .85fr;
  gap: var(--s-6) var(--s-10); align-items: end;
}
.sel__intro h1 { font-size: var(--fs-h1); max-width: 24ch; }
@media (max-width: 899px) {
  .sel__introgrid { grid-template-columns: 1fr; align-items: start; }
  .sel__intro { padding-block: calc(var(--header-h) + var(--s-5)) var(--s-5); }
  .sel__intro h1 { font-size: var(--fs-h2); }
}

/* Platform caveats — stated on the card rather than hidden, because a shorter
   platform genuinely costs shot count and the buyer should know before asking. */
.rescard__note {
  margin-top: var(--s-4);
  padding-left: var(--s-3);
  border-left: 2px solid var(--warning);
  font-size: var(--fs-body-sm);
  color: var(--text-muted);
  line-height: 1.5;
}

/* --- Result reasons ------------------------------------------------------
   The spec block (label · dotted leader · value) is built for a full-width
   column. Inside a third-width result card it wrapped every value to four
   lines and the leader dots collided with the text. Stacked instead: the label
   is a lid on the value, not a column beside it. */
.whylist { list-style: none; margin: var(--s-4) 0 0; padding: 0; display: grid; gap: var(--s-4); }
.whylist__row { display: grid; gap: 2px; }
.whylist__k { color: var(--text-muted); }
.whylist__v { color: var(--text-secondary); font-size: var(--fs-body-sm); line-height: 1.45; }
.rescard__fine {
  margin-top: var(--s-5); padding-top: var(--s-4);
  border-top: 1px solid var(--rule-01);
  font-size: var(--fs-body-sm); color: var(--text-muted);
}

/* --- What we could not check --------------------------------------------
   Heading, lede and first row were touching; the sign-off sat flush against
   the last row. Every element here now owns its own space. */
.sel__cant { margin-top: var(--space-block); }
.sel__cant-lede { margin-top: var(--s-6); max-width: 60ch; color: var(--text-secondary); }
.gaps { margin: var(--s-8) 0 0; display: grid; gap: 0; }
.gaps__row {
  display: grid; grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
  gap: var(--s-4) var(--s-8);
  padding-block: var(--s-6);
  border-top: 1px solid var(--rule-01);
}
.gaps__row:last-child { border-bottom: 1px solid var(--rule-01); }
.gaps__k { color: var(--text-muted); padding-top: 3px; }
.gaps__v { margin: 0; color: var(--text-secondary); max-width: 68ch; line-height: 1.55; }
.sel__cant-foot { margin-top: var(--s-7); color: var(--text-muted); }
@media (max-width: 719px) {
  .gaps__row { grid-template-columns: 1fr; gap: var(--s-2); }
}

/* ============================================================================
   PRODUCT DETAIL PAGE
   The markup for this page shipped without a stylesheet — .pdp, .crumbs,
   .promise* and .prose--dark had no rules at all, so every element rendered as
   a bare block with the global `* { margin:0; padding:0 }` reset. That is why
   the page read as unfinished next to the rest of the site.

   Built entirely from existing tokens and devices. No new colour, no new cut
   ratio, no new easing.
   ========================================================================= */

/* --- The page's own rhythm ----------------------------------------------
   Tighter than the site's section spacing, on purpose: this page is where a
   decision is made, and every band of empty black pushed Add to cart and the
   add-ons further from the photograph. Doubled class so a later change to
   .section's padding cannot undo it. */
.section.pdp-top { padding-top: calc(var(--header-h) + var(--s-5)); padding-bottom: var(--s-8); }
.section.pdp-maker, .section.pdp-compare { padding-block: var(--s-8); }
.section.pdp-promise { padding-block: var(--s-9); }
@media (max-width: 639px) {
  .section.pdp-top { padding-bottom: var(--s-7); }
  .section.pdp-maker, .section.pdp-compare, .section.pdp-promise { padding-block: var(--s-7); }
}

/* --- Breadcrumb --------------------------------------------------------- */
.crumbs {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3);
  padding-bottom: var(--s-4);
  border-bottom: 1px solid var(--rule-02);
  font-family: var(--font-mono); font-size: var(--fs-data);
  text-transform: uppercase; letter-spacing: .08em;
  color: var(--text-muted);
}
.crumbs a { transition: color var(--dur-hover) var(--ease-standard); min-height: 24px;
  display: inline-flex; align-items: center; }
.crumbs a:hover { color: var(--accent); }
@media (pointer: coarse) { .crumbs a { min-height: 44px; } }
.crumbs span[aria-hidden] { color: var(--text-muted); }
.crumbs [aria-current] { color: var(--text); }

/* --- The two-column top -------------------------------------------------
   Reuses the 7:5 asymmetry and 96px gap already shipped on /find-my-rifle/.
   Nothing makes a new page belong faster than repeating a proportion the
   site already uses. */
.pdp {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(380px, 5fr);
  /* Three rows, not two. The buy column spans the last two so it starts level
     with the photograph, and the specification fills the space under the
     photograph that the buy column's own height would otherwise leave empty.
     The add-ons now live inside the buy column, directly under Add to cart. */
  grid-template-areas: "head head" "media buy" "more buy";
  grid-template-rows: auto auto 1fr;
  column-gap: var(--s-9);
  align-items: start;
  margin-top: var(--s-5);
}
@media (max-width: 1279px) { .pdp { column-gap: var(--s-8); } }
@media (max-width: 1023px) {
  .pdp {
    grid-template-columns: minmax(0, 1fr);
    /* One column: name, photograph, the buy panel with its add-ons, then the
       specification. The long reading follows in its own sections. */
    grid-template-areas: "head" "media" "buy" "more";
    grid-template-rows: auto;
    row-gap: var(--s-5);
  }
}

/* --- The name band -----------------------------------------------------
   Across both columns, so the length of a product name stops deciding where
   the price sits. It repeats the page's own 7:5 split rather than inventing
   a second proportion: the name takes the width of the photograph beneath
   it, and the one-line kicker sits over the buy panel, set on the same
   baseline in the manner of the /optics/ and /pest-control/ heroes. */
.pdp__head {
  grid-area: head;
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(380px, 5fr);
  column-gap: var(--s-9);
  align-items: end;
  margin-bottom: var(--s-6);
}
@media (max-width: 1279px) { .pdp__head { column-gap: var(--s-8); } }
.pdp__maker { color: var(--text-muted); }
/* No max-inline-size: the 7fr cell is the measure, which is what makes the
   name and the photograph share one right edge. The 14ch the title used to
   carry was narrower than the column it sat in, so it wrapped twice as often
   as it needed to. */
.pdp__head h1 { margin-top: var(--s-3); max-inline-size: none; }
.pdp__kicker { max-inline-size: 38ch; }
@media (max-width: 1023px) {
  .pdp__head { grid-template-columns: minmax(0, 1fr); row-gap: var(--s-3); margin-bottom: 0; }
  .pdp__kicker { max-inline-size: var(--measure); }
}

/* Not sticky any more: the specification sits under the photograph now, and
   a sticky photograph would slide over it. */
.pdp__media { grid-area: media; }
.pdp__body { grid-area: buy; }
.pdp__more { grid-area: more; margin-top: var(--s-7); }
@media (max-width: 1023px) { .pdp__more { margin-top: 0; } }

/* --- The specimen panel -------------------------------------------------
   A constant frame with a variable object inside it. Product cutouts range
   from 4.44:1 to 0.77:1 — a 5.8x spread — so without a fixed frame the top of
   the page is a different shape on every one of 186 products. */
.pdp__panel {
  --cx: var(--cut-xl-x); --cy: var(--cut-xl-y);
  position: relative;
  aspect-ratio: 16 / 9;
  display: grid; place-items: center;
  overflow: hidden;
  /* a room, not a swatch: light above, floor below, one warm pool where the
     object meets it */
  background:
    radial-gradient(140% 90% at 50% 112%, rgba(193, 131, 47, .16) 0%, transparent 58%),
    linear-gradient(180deg, var(--surface-2) 0%, var(--bg) 62%, var(--surface-sunk) 100%);
  box-shadow: inset 0 1px 0 rgba(237, 233, 224, .05);
  clip-path: polygon(0 var(--cy), var(--cx) 0, 100% 0,
                     100% calc(100% - var(--cy)), calc(100% - var(--cx)) 100%, 0 100%);
}
/* One column: a wider frame and a ceiling, so the photograph never takes the
   whole first screen. Most of the catalogue is a 3:1 to 5:1 rifle, which a 4:3
   frame surrounded with empty floor. The width is explicit because a
   max-height otherwise carries through the ratio and narrows the frame. */
@media (max-width: 1023px) { .pdp__panel { width: 100%; aspect-ratio: 3 / 2; max-height: min(22rem, 45vh); } }

/* Same percentage-height trap as the cards: the panel's height comes from
   aspect-ratio, so `height: 78%` resolved to auto and a portrait product
   rendered far taller than the frame, clipped, and sitting low. Absolute
   positioning gives the percentages a definite box. */
.pdp__panel img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  padding: 11% 7%;
  box-sizing: border-box;
  object-fit: contain;
  object-position: center 46%;   /* seated on the floor, not floating */
  filter:
    drop-shadow(0 26px 26px rgba(0, 0, 0, .72))
    /* 1px bone rim-light along the top silhouette — without it a black rifle
       on #0A0A0B renders as an empty rectangle */
    drop-shadow(0 -1px 0 rgba(237, 233, 224, .16));
}

.pdp__fig, .pdp__id {
  position: absolute;
  font-family: var(--font-mono); font-size: var(--fs-label);
  text-transform: uppercase; letter-spacing: .1em;
  color: var(--text-muted);
  pointer-events: none;
}
.pdp__fig { top: var(--s-4); right: var(--s-4); }
.pdp__id  { bottom: var(--s-4); left: var(--s-4); max-width: 60%; }

/* No photograph on file. The panel keeps its exact dimensions and fills with
   the product's own record, so page rhythm is identical to a product that has
   one — and the spec figures become the image. */
.pdp__panel--norecord {
  background:
    radial-gradient(140% 90% at 50% 112%, rgba(193, 131, 47, .05) 0%, transparent 58%),
    var(--surface-sunk);
  box-shadow: inset 0 0 0 1px var(--rule-01);
}
.pdp__norecord {
  display: grid; justify-items: center; gap: var(--s-4);
  text-align: center; padding-inline: var(--s-7); max-width: 36ch;
}
.pdp__norecord__figs {
  font-family: var(--font-mono); font-size: var(--fs-h3);
  line-height: 1.35; color: var(--text); font-variant-numeric: tabular-nums;
}
.pdp__norecord .rule--accent { width: 100%; }

/* Scale rule — the single most useful thing a rifle photo does not tell you */
.pdp__scale {
  margin-top: var(--s-5);
  display: grid; grid-template-columns: 1fr auto 1fr;
  align-items: center; gap: var(--s-4);
  font-family: var(--font-mono); font-size: var(--fs-label);
  font-variant-numeric: tabular-nums;
  text-transform: uppercase; letter-spacing: .08em;
  color: var(--text-muted);
}
.pdp__scale::before, .pdp__scale::after {
  content: ""; height: 1px; background: var(--rule-02); position: relative;
}
.pdp__caption { margin-top: var(--s-3); }
@media (max-width: 639px) {
  .pdp__media .pdp__thumbs { margin-top: var(--s-3); }
  .pdp__media .pdp__thumb { width: 56px; }
  /* On a phone the name is already in the breadcrumb and the heading just
     above; a third copy on the photograph is noise. */
  .pdp__id { display: none; }
}

/* --- The buy block ------------------------------------------------------
   The most important 200px of a $2,600 page previously had no surface, no
   frame and no elevation. It now earns the site's own bordered-and-cut
   primitive, like every other module.

   In order: price, availability, each choice as a numbered step, a line
   saying what is still missing, Add to cart, one line for questions — and
   then the add-ons and the fits list, as compact rows. */
.pdp__buy .framed__in { padding: var(--s-5); }
@media (max-width: 479px) { .pdp__buy .framed__in { padding: var(--s-4); } }
.pdp__buy > .framed__in > .rule--accent { margin-bottom: var(--s-4); }
.pdp__oneof { margin-bottom: var(--s-3); }
.pdp__figure { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s-1) var(--s-3); }
.pdp__range {
  font-family: var(--font-mono); font-size: var(--fs-data);
  font-variant-numeric: tabular-nums; color: var(--text-secondary);
}
.pdp__buy [data-buy-status] { margin-top: var(--s-3); }

/* The choices. .vpick, .vpick__opts and .opt--chip are shared with the cart's
   delivery picker, so every rule here is scoped to the buy panel. */
.pdp__buy .vpick { margin-top: var(--s-4); }
.pdp__buy .vpick__axis { margin-bottom: var(--s-4); }
.pdp__buy .vpick__legend {
  font-family: var(--font); font-size: var(--fs-body-sm);
  font-variation-settings: 'wdth' var(--wdth-ui), 'wght' 600;
  text-transform: none; letter-spacing: 0; color: var(--text);
}
.vpick__n { font-family: var(--font-mono); color: var(--accent); }
.pdp__buy .vpick__opts {
  display: grid; gap: var(--s-2); margin-top: var(--s-2);
  grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr));
}
.pdp__buy .vpick__opts--xs { grid-template-columns: repeat(auto-fill, minmax(3rem, 1fr)); }
.pdp__buy .vpick__opts--s { grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr)); }
.pdp__buy .vpick__opts--l { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.pdp__buy .vpick__opts--xl { grid-template-columns: minmax(0, 1fr); }
/* A chip is its label, centred. The tick moves into the uncut top-right
   corner so it costs no width — selected still reads three ways: fill, edge
   bar and tick. */
.pdp__buy .opt--chip {
  min-height: 44px; padding: var(--s-2) var(--s-3); gap: 0;
  justify-content: center; text-align: center;
}
.pdp__buy .vpick__opts--xs .opt--chip { padding-inline: var(--s-2); }
.pdp__buy .opt--chip .opt__l { white-space: normal; overflow-wrap: break-word; line-height: 1.25; text-wrap: balance; }
.pdp__buy .opt--chip .opt__tick {
  position: absolute; top: var(--s-1); right: var(--s-2);
  font-size: var(--fs-label); line-height: 1;
}
.pdp__buy .vpick__note { margin-top: var(--s-3); color: var(--text-secondary); }
.pdp__buy .vpick__chosen { margin-top: var(--s-3); padding-top: 0; border-top: 0; }
/* Without scripting nothing can be added, and the noscript line says to call. */
html:not(.js) .pdp__buy [data-vpick-note] { display: none; }

.pdp__add { display: flex; width: 100%; justify-content: center; min-height: 52px; margin-top: var(--s-4); }
.pdp__buy .vpick + .pdp__add { margin-top: var(--s-3); }
.pdp__poa { margin-top: var(--s-3); max-inline-size: 40ch; }
/* Lasting proof the rifle went in, under the button that put it there. */
.pdp__incart { margin-top: var(--s-3); color: var(--text); }
.pdp__incart::before {
  content: ""; display: inline-block; width: 6px; height: 6px; margin-right: var(--s-2);
  vertical-align: middle; background: var(--success);
}
.pdp__ask { margin-top: var(--s-3); color: var(--text-secondary); }
/* The number is never split at its hyphens, and the hours take their own line
   rather than wrapping in wherever the number happened to end. */
.pdp__tel {
  color: var(--accent); white-space: nowrap;
  text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px;
  transition: color var(--dur-hover) var(--ease-standard);
}
.pdp__tel:hover { color: var(--accent-hover); }
.pdp__hours { display: block; color: var(--text-muted); }
@media (pointer: coarse) { .pdp__tel { display: inline-flex; align-items: center; min-height: 44px; } }
.pdp__reason { max-width: none; margin-top: var(--s-5); padding-top: var(--s-4); border-top: 1px solid var(--rule-01); }

/* --- Add-ons and the fits list, inside the buy panel --------------------- */
.pdp__extra { margin-top: var(--s-5); }
.pdp__h { max-width: none; font-size: var(--fs-data); line-height: 1.3; }
.pdp__lead { margin-top: var(--s-2); }
.pdp__extra .pdrows { margin-top: var(--s-3); }
.pdp__setup { margin-top: var(--s-3); font-size: var(--fs-label); }
@media (pointer: coarse) { .pdp__setup { min-height: 44px; } }

/* A row: small photograph, label and name and price, and one control. Built
   for the narrowest place it is used — a 330px buy column — so a name gets two
   lines and the control never leaves the row. */
.pdrows { list-style: none; }
.pdrow {
  display: grid; grid-template-columns: 56px minmax(0, 1fr) auto;
  align-items: center; column-gap: var(--s-3);
  padding-block: var(--s-3);
  border-top: 1px solid var(--rule-01);
}
/* display:grid beats the browser's own [hidden]; product.js hides the ammo row
   when the chosen bore has no tin on the shelf. */
.pdrow[hidden] { display: none; }
.pdrow__media {
  position: relative; display: block;
  width: 56px; aspect-ratio: 1; overflow: hidden;
  background:
    radial-gradient(120% 140% at 50% 118%, rgba(193, 131, 47, .12) 0%, transparent 62%),
    linear-gradient(180deg, var(--surface-2) 0%, var(--surface-sunk) 100%);
  box-shadow: inset 0 0 0 1px var(--rule-01);
}
/* The cutouts are transparent. The old list multiplied them into a dark
   ground, which turned every add-on photograph into a black square. */
.pdrow__media img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; padding: 6px; box-sizing: border-box;
  object-fit: contain;
  filter: drop-shadow(0 4px 6px rgba(0, 0, 0, .5)) drop-shadow(0 -1px 0 rgba(237, 233, 224, .16));
}
.pdrow__body { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.pdrow__label {
  font-family: var(--font-mono); font-size: var(--fs-label);
  text-transform: uppercase; letter-spacing: .08em; line-height: 1.3;
  color: var(--text-muted);
}
.pdrow__name {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
  font-size: var(--fs-body-sm); line-height: 1.3;
  font-variation-settings: 'wdth' var(--wdth-ui), 'wght' 500;
  color: var(--text);
  transition: color var(--dur-hover) var(--ease-standard);
}
.pdrow__name:hover { color: var(--accent); }
.pdrow__price {
  font-size: var(--fs-body-sm); line-height: 1.3;
  font-variation-settings: 'wdth' var(--wdth-ui), 'wght' 600;
  font-variant-numeric: tabular-nums;
}
.pdrow__why { margin-top: 2px; font-size: var(--fs-label); line-height: 1.45; color: var(--text-muted); }
@media (max-width: 639px) { .pdrow__why { display: none; } }
.pdrow__act { justify-self: end; }
/* Scoped to the row so no later .addbtn rule can take the 44px target away. */
.pdrow .addbtn--row { min-height: 44px; min-width: 4.5rem; padding-inline: var(--s-4); }

/* Compare runs the same rows two across once there is room for them, with a
   letterbox photograph: it lists rifles and scopes, which a square shrinks to
   a line. */
.pdrows--grid { border-bottom: 1px solid var(--rule-01); }
.pdrows--grid .pdrow { grid-template-columns: 88px minmax(0, 1fr) auto; }
.pdrows--grid .pdrow__media { width: 88px; aspect-ratio: 16 / 10; }
@media (min-width: 768px) {
  .pdrows--grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--s-8); }
}
.pdp-compare .head { margin-bottom: var(--s-5); }

/* --- Folds ---------------------------------------------------------------
   Native <details>: works without scripting and under the CSP. The summary
   reads "Read more" shut and "Show less" open. */
.pdp__fold > summary, .pdp__legal > summary { list-style: none; cursor: pointer; }
.pdp__fold > summary::-webkit-details-marker,
.pdp__legal > summary::-webkit-details-marker { display: none; }
.pdp__fold > summary { min-height: 44px; }
.pdp__cue-less, [open] > summary .pdp__cue-more { display: none; }
[open] > summary .pdp__cue-less { display: inline; }

/* --- Specification: label and value side by side, at every width --------- */
.pdp__specs { margin-top: var(--s-3); border-top: 1px solid var(--rule-01); }
.pdp__specs > div {
  display: grid; grid-template-columns: minmax(0, 9rem) minmax(0, 1fr);
  column-gap: var(--s-4); align-items: baseline;
  padding-block: var(--s-2);
  border-bottom: 1px solid var(--rule-01);
}
.pdp__specs dt {
  font-family: var(--font-mono); font-size: var(--fs-label);
  text-transform: uppercase; letter-spacing: .08em; color: var(--text-muted);
}
.pdp__specs dd { font-size: var(--fs-body-sm); line-height: 1.45; color: var(--text); font-variant-numeric: tabular-nums; }
@media (max-width: 479px) { .pdp__specs > div { grid-template-columns: minmax(0, 7rem) minmax(0, 1fr); column-gap: var(--s-3); } }
.pdp__note { margin-top: var(--s-3); }

/* Texas notice is a caution, not a promotion — semantic warning, not accent */
.callout--legal {
  background: var(--surface-1);
  box-shadow: inset 2px 0 0 var(--warning);
  padding: var(--s-4) var(--s-5);
  margin-top: var(--s-5);
}
.callout--legal .data-label { color: var(--warning); }
.callout--legal > p { margin-top: var(--s-2); }
.callout--fact { box-shadow: inset 2px 0 0 var(--rule-03); }
.callout--fact .data-label { color: var(--text-muted); }
/* The summary is the short version; the sourced wording is one tap away. */
.pdp__legal > summary { display: flex; flex-direction: column; align-items: flex-start; gap: var(--s-1); }
.pdp__legal__line { font-size: var(--fs-body-sm); line-height: 1.45; color: var(--text); }
.pdp__legal__cue { font-size: var(--fs-label); min-height: 32px; }
@media (pointer: coarse) { .pdp__legal__cue { min-height: 44px; } }
.pdp__legal__body { margin-top: var(--s-2); }

.prose--dark p { color: var(--text-secondary); max-width: var(--measure); }

/* --- From the maker ----------------------------------------------------- */
.pdp-maker__copy { margin-top: var(--s-4); }
.pdp-maker .pdp__fold { margin-top: var(--s-2); }
.pdp-maker__note { margin-top: var(--s-4); }

/* --- Bench Promise ------------------------------------------------------
   Rebuilt on the existing .numbered component instead of five classes that
   never had styles. The heading and its argument share a row, and the rows
   run two across on a desktop. */
.pdp-promise__top { display: grid; gap: var(--s-4) var(--s-9); align-items: end; }
.pdp-promise .eyebrow { margin-bottom: var(--s-3); }
.pdp-promise .rule { margin-bottom: var(--s-4); }
@media (min-width: 1024px) { .pdp-promise__top { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); } }
/* The heading alone, once the price argument beside it was removed: without
   this it sat in the left five-twelfths with an empty column to its right. */
.pdp-promise__top.pdp-promise__top--solo { grid-template-columns: minmax(0, 1fr); }
.promise__rows { display: grid; margin-top: var(--s-6); }
.promise__rows .numbered__item {
  padding-block: var(--s-4);
  border-top: 1px solid var(--rule-02);
}
/* The number sits on the bold line, not in the padding above it. */
.promise__rows .numbered__item::before { top: calc(var(--s-4) + .2em); }
@media (max-width: 639px) { .promise__rows .numbered__item { padding-left: var(--s-7); } }
@media (min-width: 1024px) {
  .promise__rows { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--s-9); }
}
.promise__bold { font-size: var(--fs-h4); font-variation-settings: 'wdth' var(--wdth-text), 'wght' 600; }
.promise__plain { margin-top: var(--s-2); color: var(--text-muted); font-size: var(--fs-body-sm); line-height: 1.55; }
.promise__close { margin-top: var(--s-4); }

/* ============================================================================
   COMMERCE — the variant picker, the gallery, the newsletter capture.

   Assembled from devices the system already owns. No new colour, no new cut
   ratio, no new easing, no new duration. The one thing genuinely absent from
   this repo is a form control, and that is built here from the 3:8 cut and
   --border-field rather than invented.
   ========================================================================= */

/* ── Variant picker ────────────────────────────────────────────────────────
   .opt is authored for bone, because the rifle finder's questions are teaching
   and teaching sits on bone. The variant picker is the same device on the other
   ground: it lives inside .pdp__buy, which is dark. .plate--on-dark is the
   existing precedent for an explicit ground modifier. */

.opt--dark {
  background: var(--surface-2);
  color: var(--text);
  box-shadow: inset 0 0 0 1px var(--border-field);
}
.opt--dark:hover { background: var(--surface-1); }
.opt--dark .opt__s { color: var(--text-muted); }

/* Selected still uses three channels — fill, edge bar, glyph — so the state
   never rests on colour alone. */
.opt--dark:has(input:checked) {
  background: var(--bone);
  color: var(--ink);
  box-shadow: inset 3px 0 0 var(--accent);
}
.opt--dark:has(input:checked) .opt__s { color: var(--ink-muted); }

/* A caliber is three characters. A full-width 56px row per value would make
   choosing between .22 and .25 feel like filling in a form. */
.opt--chip {
  min-height: 44px;              /* the floor for a touch target, still */
  padding: var(--s-3) var(--s-4);
  flex: 0 0 auto;
  gap: var(--s-3);
}
.opt--chip .opt__l { font-size: var(--fs-body-sm); white-space: nowrap; }

/* A combination the shop does not build is shown and explained, never quietly
   removed — a picker that silently drops options reads as broken. */
.opt--off {
  opacity: .5;
  cursor: not-allowed;
  box-shadow: inset 0 0 0 1px var(--rule-02);
}
.opt--off .opt__l { text-decoration: line-through; text-decoration-thickness: 1px; }
.opt--off:hover { background: var(--surface-2); }

.vpick { margin-top: var(--s-5); }
.vpick__axis { border: 0; padding: 0; margin: 0 0 var(--s-5); }
.vpick__axis:last-of-type { margin-bottom: 0; }
.vpick__legend {
  font-family: var(--font-mono); font-size: var(--fs-label);
  text-transform: uppercase; letter-spacing: .08em;
  color: var(--text-muted); padding: 0;
}
.vpick__opts { display: flex; flex-wrap: wrap; gap: var(--s-2); margin-top: var(--s-3); }
.vpick__note { margin-top: var(--s-4); font-size: var(--fs-body-sm); color: var(--text-muted); }

/* The chosen configuration, in mono, so the customer can see exactly what is
   about to go in the cart. */
.vpick__chosen {
  margin-top: var(--s-5); padding-top: var(--s-4);
  border-top: 1px solid var(--rule-01);
  font-family: var(--font-mono); font-size: var(--fs-data);
  color: var(--text-secondary);
}
.vpick__sku { color: var(--text-faint-on-s2); }

/* ── Gallery ───────────────────────────────────────────────────────────────
   The feed carries up to five photographs per product and the site rendered
   one. Thumbnails are plates at small scale, under the specimen panel. */

.pdp__thumbs {
  display: flex; flex-wrap: wrap; gap: var(--s-3);
  margin-top: var(--s-4);
}
.pdp__thumb {
  --cx: var(--cut-sm-x); --cy: var(--cut-sm-y);
  position: relative; width: 72px; aspect-ratio: 1;
  background: var(--surface-2); padding: 1px; cursor: pointer;
  clip-path: polygon(0 var(--cy), var(--cx) 0, 100% 0,
                     100% calc(100% - var(--cy)), calc(100% - var(--cx)) 100%, 0 100%);
  transition: background var(--dur-hover) var(--ease-standard);
}
.pdp__thumb:hover { background: var(--rule-03); }
.pdp__thumb[aria-current="true"] { background: var(--accent); }
.pdp__thumb img {
  position: absolute; inset: 1px;
  width: calc(100% - 2px); height: calc(100% - 2px);
  object-fit: contain; padding: 6px;
  background: var(--surface-1);
}

/* ── Form controls ─────────────────────────────────────────────────────────
   The repo had none: no text input, no email input, no textarea, no select and
   no label style existed anywhere before this. Built from the same 3:8 cut as
   .btn, bordered with --border-field because that is the only colour allowed to
   communicate the bounds of an interactive control, and square-cornered because
   border-radius is zero everywhere on this site. */

.field { display: block; margin-bottom: var(--s-5); }
.field__label {
  display: block; margin-bottom: var(--s-3);
  font-family: var(--font-mono); font-size: var(--fs-label);
  text-transform: uppercase; letter-spacing: .08em;
  color: var(--text-muted);
}
.field__req { color: var(--accent); margin-left: .35em; }

.field__input,
.field__select,
.field__textarea {
  --cx: var(--cut-sm-x); --cy: var(--cut-sm-y);
  display: block; width: 100%;
  min-height: 48px; padding: var(--s-3) var(--s-4);
  background: var(--surface-2); color: var(--text);
  font-family: var(--font); font-size: var(--fs-body);
  font-variation-settings: 'wdth' var(--wdth-text), 'wght' 400;
  border: 0; border-radius: 0;
  box-shadow: inset 0 0 0 1px var(--border-field);
  clip-path: polygon(0 var(--cy), var(--cx) 0, 100% 0,
                     100% calc(100% - var(--cy)), calc(100% - var(--cx)) 100%, 0 100%);
  transition: box-shadow var(--dur-hover) var(--ease-standard);
}
.field__textarea { min-height: 140px; padding: var(--s-4); line-height: 1.6; resize: vertical; }
/* The field's own ground is --surface-2, where --text-faint measures 3.96:1.
   These are the hints telling a customer what format to type into the card
   fields, so they were the least legible text on the page they hand a card
   number to. */
.field__input::placeholder, .field__textarea::placeholder { color: var(--text-faint-on-s2); }
.field__input:hover, .field__select:hover, .field__textarea:hover { box-shadow: inset 0 0 0 1px var(--text-faint); }


/* A native select keeps the platform's own popup — which on a phone is the
   accessible, familiar one — and only the closed state is restyled. */
.field__select {
  appearance: none;
  padding-right: var(--s-8);
  background-image: linear-gradient(45deg, transparent 50%, var(--text-muted) 50%),
                    linear-gradient(135deg, var(--text-muted) 50%, transparent 50%);
  background-position: calc(100% - 20px) 55%, calc(100% - 14px) 55%;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
}
.field__select option { background: var(--surface-1); color: var(--text); }

.field__hint { margin-top: var(--s-2); font-size: var(--fs-body-sm); color: var(--text-muted); }

/* Error borrows .checkerr's language — a danger edge bar and a wash, never a
   red border alone, because colour is not allowed to carry meaning by itself. */
.field--err .field__input,
.field--err .field__select,
.field--err .field__textarea { box-shadow: inset 0 0 0 1px var(--danger), inset 3px 0 0 var(--danger); }
.field__err {
  margin-top: var(--s-2); padding: var(--s-2) var(--s-3);
  background: rgba(217, 85, 60, .12);
  box-shadow: inset 2px 0 0 var(--danger);
  font-size: var(--fs-body-sm); color: var(--text);
}

/* ── Newsletter capture ────────────────────────────────────────────────────
   The site's first posting form. It sits on bone, because it is an invitation
   rather than product theatre, and the token remap means the controls above
   need no bone-specific rules. */

.nl { display: grid; grid-template-columns: 7fr 5fr; gap: var(--s-10); align-items: center; }
@media (max-width: 899px) { .nl { grid-template-columns: 1fr; gap: var(--space-block); } }

.nl__form { display: flex; gap: var(--s-3); align-items: flex-start; flex-wrap: wrap; }
.nl__form .field { flex: 1 1 16rem; margin-bottom: 0; }
.nl__form .btn { flex: 0 0 auto; min-height: 48px; }
.nl__small { margin-top: var(--s-4); font-size: var(--fs-body-sm); color: var(--text-muted); max-inline-size: 52ch; }

/* The honeypot. Off-screen rather than display:none, because some autofill
   agents skip hidden fields and a bot that fills this one identifies itself. */
.nl__hp {
  position: absolute; left: -9999px; width: 1px; height: 1px;
  overflow: hidden; opacity: 0;
}

.nl__msg { margin-top: var(--s-4); font-size: var(--fs-body-sm); }
.nl__msg--ok { color: var(--success); }
.nl__msg--err { color: var(--danger); }
.section--bone .nl__msg--ok { color: var(--ink); }
.section--bone .nl__msg--err { color: var(--danger); }

/* ── Limited builds ────────────────────────────────────────────────────────
   No starburst, no countdown, no "only 2 left". A one-of-one is marked the way
   the shop would mark it on the bench: a stamped seal and the plain fact. */

.oneof {
  display: inline-flex; align-items: center; gap: var(--s-2);
  font-family: var(--font-mono); font-size: var(--fs-label);
  text-transform: uppercase; letter-spacing: .12em;
  color: var(--accent);
}
.oneof::before {
  content: ""; width: 8px; height: 8px; flex: none;
  background: var(--accent);
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
}
.pcard__flag { margin-bottom: var(--s-3); }


/* ── Focus, on a clipped control ───────────────────────────────────────────
   The design system says focus is never removed, and it has been — silently,
   everywhere, since the 3:8 cut shipped. `clip-path` clips an element's own
   painting to the polygon, and `outline-offset` puts the ring *outside* it, so
   `outline: 2px solid var(--accent)` on .btn, .opt, .opt--dark, .field__input
   and .pdp__thumb draws nothing at all. Verified in a browser against two
   identical probes, one clipped and one not: the unclipped one rings, the
   clipped one does not.

   An inset box-shadow paints inside the clip region, so it survives. Every cut
   control below rings itself from the inside, at the same 2px and the same
   accent, and the outline is explicitly dropped so nothing half-paints. */

.btn:focus-visible,
.addbtn:focus-visible,
a.framed:focus-visible,
.opt:has(input:focus-visible),
.opt--dark:has(input:focus-visible),
.pdp__thumb:focus-visible,
.field__input:focus-visible,
.field__select:focus-visible,
.field__textarea:focus-visible {
  outline: none;
}

/* On a bone or accent fill the ring has to be the dark ink, or it disappears
   into the fill it is drawn on. */
.btn--primary:focus-visible,
.btn--bone:focus-visible { box-shadow: inset 0 0 0 2px var(--accent-ink); }

.btn--ghost:focus-visible,
.btn:not(.btn--primary):not(.btn--bone):focus-visible {
  box-shadow: inset 0 0 0 1px var(--border-field), inset 0 0 0 3px var(--accent);
}

.opt:has(input:focus-visible) { box-shadow: inset 0 0 0 1px var(--bone-line), inset 0 0 0 3px var(--ink-accent); }
.opt:has(input:checked):has(input:focus-visible) { box-shadow: inset 3px 0 0 var(--accent), inset 0 0 0 3px var(--bone); }

.opt--dark:has(input:focus-visible) { box-shadow: inset 0 0 0 1px var(--border-field), inset 0 0 0 3px var(--accent); }
.opt--dark:has(input:checked):has(input:focus-visible) { box-shadow: inset 3px 0 0 var(--accent), inset 0 0 0 3px var(--ink); }

.pdp__thumb:focus-visible { background: var(--accent); box-shadow: inset 0 0 0 2px var(--bg); }

.field__input:focus-visible,
.field__select:focus-visible,
.field__textarea:focus-visible { box-shadow: inset 0 0 0 2px var(--accent); }

/* .addbtn carries the same cut as .btn and was left out of the list above, so
   it inherited the global outline and drew nothing at all. On /shop/ that is
   every one of the 132 "Add to cart" buttons: focus landed on one and the page
   did not change, so a keyboard buyer could not tell which card was armed
   before pressing Space. */
.addbtn:focus-visible {
  box-shadow: inset 0 0 0 1px var(--border-field), inset 0 0 0 3px var(--accent);
}

/* Pressed, the button turns green and keeps focus for the 1600ms the "Added"
   label is up. Accent on that fill is all but invisible, so the ring becomes
   the dark ink — the same rule the bone and accent fills follow above. */
.addbtn.is-added:focus-visible { box-shadow: inset 0 0 0 2px var(--accent-ink); }

/* The mission cards are anchors, and .framed is a clipped 1px border layer, so
   their outline was clipped away too. Colouring that layer rings the cut edge
   itself, and the inner surface carries the 2px so the result is a ring rather
   than a hairline that reads as an ordinary border. */
a.framed:focus-visible { background: var(--accent); }
a.framed:focus-visible > .framed__in { box-shadow: inset 0 0 0 2px var(--accent); }

/* ── Choose, rather than add ───────────────────────────────────────────────
   A card for a product with more than one configuration must not add anything:
   it sends the reader to the page where the choice exists. It is a link, so it
   is styled as the button it replaces rather than as a second kind of control. */
.addbtn--choose { text-decoration: none; }

/* Two fields on one row, collapsing on a narrow column. 639 is the mobile
   breakpoint the rest of the site already uses. */
.field-row { display: flex; flex-wrap: wrap; gap: var(--s-4); }
.field-row .field--half { flex: 1 1 12rem; }
@media (max-width: 639px) { .field-row { display: block; } }

/* Checkout order lines. .cline is the cart's; this is its quieter sibling in
   the summary panel, where nothing is editable. */
.ckline { display: flex; gap: var(--s-4); align-items: baseline;
  padding: var(--s-4) 0; border-bottom: 1px solid var(--rule-01); }
.ckline:last-child { border-bottom: 0; }
.ckline__body { flex: 1; min-width: 0; }
.ckline__name { font-variation-settings: 'wdth' var(--wdth-ui), 'wght' 500; }
.ckline__variant { margin-top: 2px; color: var(--text-muted); }
/* The summary panel is --surface-1: this is the per-unit price the customer
   is about to be charged, and it was reading at 4.28:1. */
.ckline__qty { margin-top: 2px; color: var(--text-faint-on-s2); }
.ckline__amt { font-family: var(--font-mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
.cline__variant { margin-top: 2px; color: var(--text-muted); }
.cline__note { margin-top: var(--s-2); color: var(--warning); }

/* ── The cart page ─────────────────────────────────────────────────────────
   Layout only. public/cart.js finds everything by id and toggles `hidden`. */

/* .spec__row sets display:flex, which beats the browser's own [hidden], so the
   discount row cart.js hides was drawn anyway, reading "Discount —". */
.spec__row[hidden] { display: none; }
/* Same fault on the card form: checkout.js expired() hides #ck-main, a .grid,
   and the grid's display kept the whole card form on screen under "This
   checkout has expired". Display only; no script reads it. */
.cart-grid[hidden] { display: none; }

.cartempty {
  padding: var(--s-6);
  background: var(--surface-1);
  box-shadow: inset 0 0 0 1px var(--rule-02);
}
.cartempty__t {
  font-size: var(--fs-h3);
  font-variation-settings: 'wdth' var(--wdth-ui), 'wght' 600;
  line-height: 1.2;
}
@media (max-width: 639px) { .cartempty { padding: var(--s-5); } }

.cartsum { padding: var(--s-6); }
@media (max-width: 639px) { .cartsum { padding: var(--s-5) var(--s-4); } }
.cartcode { margin-top: var(--s-3); }
.cartsum__totals {
  margin-top: var(--s-5); padding-top: var(--s-4);
  border-top: 1px solid var(--rule-02);
}
/* The number the button is about to ask for, at the size of a price. */
.cartsum__total { margin-top: var(--s-2); }
.cartsum__total .spec__label { color: var(--text); }
.cartsum__total .spec__val { font-size: var(--fs-body-lg); }

/* Nothing in the cart, nothing to agree to. Where :has() is not supported the
   terms simply stay, as they always did. */
#cart-root:has(#cart-empty:not([hidden])) + .cartterms { display: none; }

/* The quantity and Remove controls were 36px and 24px, and a thumb needs 44. */
@media (pointer: coarse) {
  .qty { width: 44px; height: 44px; }
  .cline__rm { min-height: 44px; }
}

/* ── A cart a thumb can get through ────────────────────────────────────────
   Three lines and three delivery choices put "Checkout securely" 2,100px down
   a phone: each line stacked name, option, unit price, controls and total in
   five rows, and each delivery choice was a 110px card. On a phone the line
   total sits beside the name, the controls take one row under it, and a
   delivery choice is one tight row. Markup unchanged — cart.js draws both. */
@media (max-width: 639px) {
  .cline {
    grid-template-columns: 56px minmax(0, 1fr) auto;
    grid-template-areas: "media body amt";
    column-gap: var(--s-3);
    align-items: start;
    padding-block: var(--s-4);
  }
  .cline__media { grid-area: media; }
  .cline__body { grid-area: body; min-width: 0; }
  .cline__amt { grid-area: amt; margin-top: 0; text-align: right; font-size: var(--fs-body); }
  .cline__name { font-size: var(--fs-body); }
  .cline__unit { margin-top: 2px; }
  .cline__ctl { margin-top: var(--s-2); }
  .cline__rm { margin-left: var(--s-2); }

  #cart-shipping { gap: var(--s-2); }
  #cart-shipping .opt { min-height: 48px; padding: var(--s-3) var(--s-4); gap: var(--s-3); }
  #cart-shipping .opt__l { font-size: var(--fs-body-sm); line-height: 1.3; }
}

/* One discount code at a time, and swapping one for another is a question.
   The shop can hold exactly one code — that is what the order records — so a
   customer typing a second one is choosing between them, not adding. Asked
   here rather than answered for them, because the code they already have may
   be the better one and only they know that. */
.cartswap {
  margin-top: var(--s-4);
  padding: var(--s-4);
  border: 1px solid var(--rule-02);
  background: var(--surface-2);
}
.cartswap__row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  margin-top: var(--s-3);
}
/* Both answers are real answers, so neither is a footnote: they sit at the same
   size and the same height, and on a phone they stack rather than squeeze. */
.cartswap__row .btn { flex: 1 1 10rem; justify-content: center; min-height: 44px; }
