/* ============================================================================
   ARROYO CUSTOM RIGS — PUBLIC STOREFRONT STYLES  (Demo A structure)
   Depends on arroyo.css for design tokens (:root). Public pages only.
   ============================================================================ */
*{box-sizing:border-box}
html,body{background:var(--bg);color:var(--ink);font-family:var(--font-body);-webkit-font-smoothing:antialiased}
/* overflow-x must be on BOTH: with `html` left visible the viewport still pans
   sideways on touch even though `body` is hidden. `clip` (not `hidden`) so we
   don't turn the root into a scroll container and break `position:sticky` nav. */
html{overflow-x:clip}
body{margin:0;overflow-x:clip;line-height:1.55}
/* The gradient is a placeholder that shows while a photo loads. It is painted BEHIND
   the image, so any transparent asset (the logo, future badges) shows it through the
   alpha as a dark box — opt those out with .is-transparent rather than dropping it. */
img{max-width:100%;display:block;background:linear-gradient(135deg,#1a1820 0%,#2a2430 50%,#1a1820 100%)}
img.is-transparent{background:none}
a{color:var(--ink);text-decoration:none}

/* Film grain overlay (subtle) */
body::before{
  content:"";position:fixed;inset:0;pointer-events:none;z-index:9999;opacity:.06;mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence baseFrequency='0.9' numOctaves='2'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
}

/* Type */
.display{font-family:var(--font-display);font-weight:400;letter-spacing:-.01em;line-height:.92;text-transform:uppercase}
.stencil{font-family:var(--font-stencil);letter-spacing:.08em;text-transform:uppercase}
.eyebrow{font-family:var(--font-stencil);font-size:.78rem;letter-spacing:.22em;text-transform:uppercase;color:var(--gold)}
.eyebrow::before{content:"// ";color:var(--gold);opacity:.7}
h1,h2,h3,h4{font-family:var(--font-display);font-weight:400;letter-spacing:-.005em;line-height:.95;text-transform:uppercase;margin:0}

/* Skip */
.skip{position:absolute;left:-9999px;top:0;background:var(--pink);color:var(--ink);padding:.6rem 1rem;z-index:10000}
.skip:focus{left:1rem;top:1rem}

/* Brand mark. One asset serves both the nav and the footer because both grounds are
   --gold. The 2026-08-04 recreation was rolled back for two reasons that do not apply
   here: its keyline was white and read as a sticker on gold (this mark's keyline is
   red), and the footer was near-black then, which swallowed the black letterforms —
   the footer is gold now. If either ground ever goes dark again this needs a reversed
   variant, not a filter. See _designs/_LOGO-HANDOFF.md.
   Sized by height so the mark stays on one optical baseline as the bar shrinks; the
   width/height attributes on the <img> reserve the box so the sticky nav doesn't jump. */
.brand{display:inline-flex;align-items:center}
.brand-logo{display:block;height:55px;width:auto;background:none}
footer .brand-logo{height:72px}

/* Top utility bar (social + basket) */
.topbar{background:#000;border-bottom:1px solid var(--line)}
.topbar-inner{display:flex;align-items:center;justify-content:space-between;gap:1rem;max-width:var(--maxw);margin:0 auto;padding:.4rem 2rem;min-height:38px}
.topbar-social{display:flex;align-items:center;gap:1rem}
.topbar-tag{font-family:var(--font-stencil);font-size:.66rem;letter-spacing:.2em;text-transform:uppercase;color:var(--muted);margin-right:.4rem}
/* WCAG 2.2 AA — 2.5.8 Target Size (Minimum) is 24x24 CSS px, and these icons
   draw at 16px. The GLYPH stays 16px; only the tappable box grows, so nothing
   about the design changes — it just stops being a 16px target on a phone.
   (24 is the conformance bar. The 44px figure quoted by most tools is Apple's
   usability guidance, not WCAG.) */
.topbar .soc{display:inline-flex;align-items:center;justify-content:center;min-width:24px;min-height:24px;color:var(--muted);transition:color .15s, transform .15s}
.topbar .soc:hover{color:var(--gold);transform:translateY(-1px)}
.topbar-actions{display:flex;align-items:center;gap:1rem}
.topbar .topbar-cta{display:none}   /* desktop keeps the nav CTA; mobile shows this one */
.topbar .basket{position:relative;display:inline-flex;align-items:center;justify-content:center;min-width:24px;min-height:24px;color:var(--ink);transition:color .15s}
.topbar .basket:hover{color:var(--gold)}
.topbar .basket-count{position:absolute;top:-8px;right:-10px;min-width:16px;height:16px;padding:0 4px;background:var(--pink);color:#fff;border-radius:9px;font-family:var(--font-body);font-weight:800;font-size:.62rem;line-height:16px;text-align:center}
/* Call-the-shop dropdown — native <details>, opens on click only (never hover).
   Numbers come from config/shop.php; see partials/call-dropdown.blade.php. */
.calldrop{position:relative;display:inline-flex}
.calldrop-toggle{list-style:none;cursor:pointer;gap:.15rem;min-height:24px}
.calldrop-toggle::-webkit-details-marker{display:none}   /* Safari's default triangle */
.calldrop-caret{transition:transform .15s;opacity:.7}
.calldrop[open] .calldrop-toggle{color:var(--gold)}
.calldrop[open] .calldrop-caret{transform:rotate(180deg)}
.calldrop-panel{position:absolute;top:calc(100% + .55rem);left:50%;transform:translateX(-50%);z-index:200;
  min-width:210px;padding:.7rem;background:var(--panel);border:1px solid var(--line);border-radius:10px;
  box-shadow:0 18px 40px rgba(0,0,0,.55)}
/* Little pointer up to the icon. */
.calldrop-panel::before{content:"";position:absolute;top:-5px;left:50%;margin-left:-5px;width:9px;height:9px;
  background:var(--panel);border-left:1px solid var(--line);border-top:1px solid var(--line);transform:rotate(45deg)}
.calldrop-head{font-family:var(--font-stencil);font-size:.6rem;letter-spacing:.18em;text-transform:uppercase;
  color:var(--muted);margin:0 0 .5rem;padding:0 .35rem}
.calldrop-panel ul{list-style:none;margin:0;padding:0}
.calldrop-panel li + li{margin-top:.15rem}
.calldrop-panel li a{display:flex;flex-direction:column;gap:.05rem;padding:.4rem .35rem;border-radius:7px;
  color:var(--ink);text-decoration:none;transition:background .15s,color .15s}
.calldrop-panel li a:hover,.calldrop-panel li a:focus-visible{background:var(--panel-2);color:var(--gold)}
.calldrop-who{font-family:var(--font-stencil);font-size:.66rem;letter-spacing:.14em;text-transform:uppercase;color:var(--muted)}
.calldrop-panel li a:hover .calldrop-who,.calldrop-panel li a:focus-visible .calldrop-who{color:var(--gold)}
.calldrop-num{font-weight:700;font-size:.9rem;letter-spacing:.01em}
.calldrop-alt{display:block;margin-top:.5rem;padding:.4rem .35rem 0;border-top:1px solid var(--line);
  font-size:.72rem;color:var(--muted);text-decoration:none}
.calldrop-alt:hover,.calldrop-alt:focus-visible{color:var(--gold)}

@media (max-width: 720px){
  .topbar-inner{padding:.35rem 1.5rem}
  .topbar-tag{display:none}
  /* Move the CTA up to the top bar (after the social icons) */
  .topbar .topbar-cta{display:inline-flex;padding:.3rem .6rem;font-size:.58rem;letter-spacing:.1em;gap:.35rem;margin-left:.35rem}
  /* Hide the nav CTA so the row is just logo + hamburger → hamburger goes right */
  .nav .btn-pink{display:none}
  /* The social icons sit at the LEFT of the bar once .topbar-tag is hidden, so
     the panel anchors left — centring it (or anchoring right) pushes it off the
     screen edge and clips the numbers. */
  .calldrop-panel{left:0;right:auto;transform:none;min-width:190px;max-width:calc(100vw - 3rem)}
  .calldrop-panel::before{left:14px;margin-left:0}
}

/* Nav — gold bar with dark text */
.nav{position:sticky;top:0;z-index:100;background:var(--gold);border-bottom:1px solid rgba(0,0,0,.22)}
.nav-inner{display:flex;align-items:center;justify-content:space-between;padding:1.1rem 2rem;max-width:var(--maxw);margin:0 auto;gap:2rem}
.nav-menu{display:flex;gap:2rem;list-style:none;margin:0;padding:0}
.nav-menu a{font-family:var(--font-body);font-weight:700;font-size:.82rem;letter-spacing:.18em;text-transform:uppercase;color:#17110A;transition:color .15s,border-color .15s;border-bottom:2px solid transparent;padding-bottom:3px}
.nav-menu a:hover,.nav-menu a.active{color:#17110A;border-bottom-color:var(--red)}
/* Dark-on-gold treatment for the toggle inside the nav */
.nav .nav-toggle{color:#17110A}
.btn-pink{
  display:inline-flex;align-items:center;gap:.55rem;font-family:var(--font-body);font-weight:800;font-size:.78rem;letter-spacing:.2em;text-transform:uppercase;
  background:var(--pink);color:#fff;padding:.85rem 1.4rem;border:0;cursor:pointer;
  clip-path:polygon(0 0, calc(100% - 10px) 0, 100% 10px, 100% 100%, 10px 100%, 0 calc(100% - 10px));
  transition:background .15s, transform .15s;
}
.btn-pink:hover{background:var(--pink-2);transform:translateY(-1px)}
/* A disabled button has to LOOK disabled.
   Client, 2026-08-13, on /contact: "why is the send button active without me
   clicking on the radio?" — it was not active. consent-gate.js had already set
   disabled=true, but the button still rendered full brand red at opacity 1 with
   a pointer cursor, so a visitor who skipped the checkbox clicked a
   normal-looking button, got nothing, and had no idea why. On a contact form
   that is a lost customer.
   Greyed rather than dimmed: a translucent red still reads as "red button",
   where a grey one reads as "not yet". */
.btn-pink:disabled,
.btn-pink[disabled]{
  background:#8A8A8A;color:rgba(255,255,255,.75);
  cursor:not-allowed;transform:none;
}
.btn-pink:disabled:hover,
.btn-pink[disabled]:hover{background:#8A8A8A;transform:none}

/* Says WHY the button is off, next to the button. A disabled control fires no
   click event, so it cannot explain itself when pressed — the reason has to be
   on screen already. Hidden once the box is ticked. */
.consent-hint{
  display:block;margin-top:.6rem;font-size:.82rem;color:var(--muted,#9a9a9a);
}
.consent-hint[hidden]{display:none}
.btn-ghost{
  display:inline-flex;align-items:center;gap:.55rem;font-family:var(--font-body);font-weight:800;font-size:.78rem;letter-spacing:.2em;text-transform:uppercase;
  background:transparent;color:var(--ink);padding:.85rem 1.4rem;border:1px solid var(--ink);cursor:pointer;
  transition:background .15s, color .15s;
}
.btn-ghost:hover{background:var(--ink);color:var(--bg)}
.nav-toggle{display:none;background:none;border:0;color:var(--ink);font-size:1.5rem;cursor:pointer}

/* Hero slideshow */
/* svh, not vh: on mobile browsers vh is the *large* viewport (URL bar hidden),
   so a 92vh hero is taller than the visible area on load and the controls
   pinned to its bottom edge start off-screen. vh first as the fallback. */
.hero-slider{position:relative;min-height:92vh;min-height:92svh;overflow:hidden}
/* WebGL distortion layer */
.hero-gl{position:absolute;inset:0;z-index:0;width:100%;height:100%;display:block}
.hero-slider.gl-active .hero-bg{display:none}
.hero-slider.gl-active .hero-slide{background:transparent}
/* Contrast box wrapping just the hero text (solid panel, no fade) */
.hero-box{display:inline-block;max-width:min(100%, 640px);
  background:rgba(11,10,12,.62);border-left:3px solid var(--gold);
  padding:1.8rem 2rem;
  -webkit-backdrop-filter:blur(16px) saturate(135%);
  backdrop-filter:blur(16px) saturate(135%);
  box-shadow:0 8px 40px rgba(0,0,0,.35),inset 0 1px 0 rgba(245,241,234,.06)}
/* Copy on the right for slides carrying --text-right. Flex rather than text-align:
   text-align would right-align the copy INSIDE the box too, and the headline, lede
   and CTA row all need to stay left-ranged. justify-content moves only the box.
   The gold rule stays on the left edge of the box on purpose — it reads as a margin
   marker against the copy, not as an outer frame, so it should not flip with the box.
   Reserves room for .hero-arrow.next (52px at right:1.2rem), which is centred
   vertically and would otherwise sit on top of the box on mid-width screens. */
.hero-slide--text-right .hero-content{display:flex;justify-content:flex-end;
  padding-right:clamp(0rem,calc((100vw - var(--maxw)) / -2 + 4.2rem),4.2rem)}
/* Hero accent text is gold (not red) */
.hero-slider .eyebrow,.hero-slider .eyebrow::before{color:var(--gold)}
.hero-track{position:relative;min-height:92vh;min-height:92svh}
.hero-slide{position:absolute;inset:0;min-height:92vh;min-height:92svh;display:flex;align-items:flex-end;padding:6rem 2rem 5rem;opacity:0;visibility:hidden;transition:opacity .8s ease}
.hero-slide.is-active{opacity:1;visibility:visible;position:relative}
.hero-arrow{position:absolute;top:50%;transform:translateY(-50%);z-index:5;width:52px;height:52px;display:flex;align-items:center;justify-content:center;background:rgba(11,10,12,.5);border:1px solid var(--line);color:var(--ink);font-size:2rem;line-height:1;cursor:pointer;transition:background .15s,color .15s;backdrop-filter:blur(6px)}
.hero-arrow:hover{background:var(--pink);border-color:var(--gold);color:#fff}
.hero-arrow.prev{left:1.2rem}
.hero-arrow.next{right:1.2rem}
/* Off the hero at every size, at the client's request — but still in the DOM,
   still keyboard-reachable, and it flies in on focus. The slideshow advances on
   its own, so WCAG 2.2.2 needs *some* mechanism to stop it; this is the same
   show-on-focus treatment as .skip above, so nothing is visible to a mouse or
   touch visitor while keyboard and screen-reader users keep the control. */
.hero-pause{position:absolute;left:-9999px;top:0;z-index:6;background:rgba(11,10,12,.9);
  border:1px solid var(--line);color:var(--ink);font-family:var(--font-body);font-size:.7rem;
  font-weight:800;letter-spacing:.14em;text-transform:uppercase;padding:.55rem .9rem;cursor:pointer}
.hero-pause:focus{left:auto;right:1.2rem;top:1.2rem}
.hero-pause:hover,.hero-pause:focus{border-color:var(--gold);color:var(--gold)}
.hero-dots{position:absolute;bottom:1.6rem;left:50%;transform:translateX(-50%);z-index:5;display:flex;gap:.6rem}
/* 2.5.8: a 5px-tall dot is an unusable target on a phone. Transparent
   vertical padding lifts the hit box to 24px while background-clip keeps the
   painted dot exactly 5px — the design is unchanged, the target is not. */
.hero-dot{width:34px;height:5px;box-sizing:content-box;background:rgba(245,241,234,.3);background-clip:content-box;border:0;cursor:pointer;padding:9.5px 0;transition:background .2s}
.hero-dot.is-active{background:var(--gold)}
.hero-dot:hover{background:var(--gold)}
/* Hero (shared slide internals) */
.hero{position:relative;min-height:92vh;display:flex;align-items:flex-end;overflow:hidden;padding:6rem 2rem 4rem}
.hero-bg{position:absolute;inset:0;z-index:0}
.hero-bg img{width:100%;height:100%;object-fit:cover;object-position:center}
.hero-content{position:relative;z-index:2;max-width:var(--maxw);width:100%;margin:0 auto}
.hero h1{font-size:clamp(3.5rem,11vw,11rem);max-width:14ch}
.hero h1 .out{color:transparent;-webkit-text-stroke:2px var(--ink);display:block}
.hero .lede{max-width:48ch;color:var(--muted);font-size:1.05rem;margin:1.8rem 0 2.3rem;line-height:1.6}
/* The slideshow hero is .hero-slider > .hero-track > .hero-slide — there is no
   .hero ancestor, so none of the three rules above ever matched it and the
   headline fell back to the browser default 2em. It also needs its own scale
   rather than the boxless hero's 11vw: this copy lives inside .hero-box, which
   caps at 640px. */
.hero-slider h1{font-size:clamp(2.4rem,4vw + .8rem,4rem);max-width:16ch}
/* inline, not block: the outline word stays on the same line as the text before
   it ("DRIVEN BY YOU." on one line) at every breakpoint. The line break above it
   comes from the newline in the heading field, so editors still control it. */
.hero-slider h1 .out{color:transparent;-webkit-text-stroke:2px var(--ink);display:inline}
.hero-slider .lede{max-width:46ch;color:var(--muted);font-size:1.02rem;margin:1.2rem 0 1.8rem;line-height:1.6}
.hero-ctas{display:flex;gap:1rem;flex-wrap:wrap}
.hero-eyebrow{display:inline-flex;align-items:center;gap:.7rem;margin-bottom:1.5rem}
.hero-eyebrow .bar{width:48px;height:2px;background:var(--gold)}

/* ---- Hero in portrait: split layout -------------------------------------
   Desktop overlays the copy on the photo. That does not survive a portrait
   viewport: these are 16:9 shots, so `cover` on a 390×776 box crops to a ~28%
   centre strip of the frame — the subject ends up hidden behind .hero-box and
   what's left visible is out-of-focus background. Three further faults: the
   arrows sat at top:50%, i.e. straight through the copy; the dots and Pause
   were pinned to the bottom of a 92vh slider and so landed BELOW the fold on
   an 844px phone; and the slide kept its 6rem/5rem desktop padding.

   So in portrait the hero splits: the photo gets its own 16:9 band at the top
   (uncropped for a photo shot to the landscape policy), and the copy sits
   underneath on a solid panel — legible regardless of which photo the client
   uploads. Controls form one thumb-reachable row at the bottom. --band is
   consumed by .hero-slide's padding and by .hero-gl/.hero-bg, so the band
   height is set in exactly one place.

   Keyed on ORIENTATION, not a width breakpoint. The fault is a tall-and-narrow
   viewport, not a small one: iPad portrait at 768×1024 is wide enough to miss
   any phone breakpoint yet still crops the photo to 0.80 and pushes the dots
   past the fold. max-width caps it so a tall desktop window keeps the overlay.
   ------------------------------------------------------------------------ */
@media (orientation: portrait) and (max-width: 1024px){
  /* The band is a fixed 16:9 so a photo shot to the studio's landscape policy
     shows UNCROPPED. It has to be fixed rather than per-slide: slide 1 is the
     16:9 hero record, but the rest are generated from products flagged
     "Show in hero", whose art runs 3:2 and even 4:5 portrait. Sizing each band
     to its own image would swing the hero between ~219px and ~487px tall and
     shunt the page content down on every autoplay tick. */
  .hero-slider{--ar-w:16;--ar-h:9;--band:calc(100vw * var(--ar-h) / var(--ar-w));
    min-height:0;background:var(--bg)}
  .hero-track{min-height:0}
  /* display:block, not the desktop flex/align-end — copy flows under the band.
     Percentage padding resolves against the slide's own width, so the reserved
     space tracks the band exactly without depending on 100vw. */
  .hero-slide{min-height:0;display:block;
    padding:calc(100% * var(--ar-h) / var(--ar-w)) 0 4.75rem}
  /* Confine the photo (WebGL canvas, or the <img> on the no-WebGL path) to the
     band instead of the whole slider. */
  .hero-gl,.hero-bg{top:0;bottom:auto;height:auto;width:100%;
    aspect-ratio:var(--ar-w)/var(--ar-h)}
  .hero-bg img{height:100%}
  .hero-content{max-width:none}
  /* The first slide right-aligns its box on desktop. In the split layout the copy
     is a full-width panel under the photo band, so there is nothing to align — undo
     it, or the box shrink-wraps to its text and sits off to one side. */
  .hero-slide--text-right .hero-content{display:block;padding-right:0}
  /* Opaque panel: with the photo no longer behind it, the blur+translucency
     was only costing legibility and paint time. */
  .hero-box{width:100%;max-width:none;padding:1.4rem 1.15rem 1.5rem;
    background:var(--panel);-webkit-backdrop-filter:none;backdrop-filter:none;
    box-shadow:none;border-left:3px solid var(--gold)}
  .hero-slider h1{font-size:clamp(2.1rem,9.5vw,3.1rem);max-width:none}
  .hero-slider .lede{font-size:.95rem;margin:.9rem 0 1.35rem;max-width:none}
  /* 2px stroke reads as a smear at phone headline sizes. */
  .hero-slider h1 .out{-webkit-text-stroke-width:1.5px}
  .hero-eyebrow{margin-bottom:.9rem}
  .hero-eyebrow .bar{width:26px}
  .hero-slider .eyebrow{font-size:.68rem;letter-spacing:.16em}
  /* Stacked, equal width — the two were 211px and 160px, which read as a
     mistake rather than a hierarchy. */
  .hero-ctas{flex-direction:column;gap:.6rem}
  .hero-ctas>a{width:100%;justify-content:center}

  /* Control row, below the copy */
  .hero-arrow{top:auto;bottom:1rem;transform:none;width:44px;height:44px;font-size:1.5rem}
  .hero-arrow.prev{left:1.15rem}
  .hero-arrow.next{right:1.15rem}
  /* Centred against the 44px arrows: 1rem + (44-5)/2 ≈ 2.2rem. */
  .hero-dots{bottom:2.2rem}
  .hero-dot{width:26px;height:5px}
  /* Nothing here for .hero-pause — it is hidden-until-focus at every size now
     (see the base rule). Arrows and dots are the only visible controls. */
}
/* Below ~380px the dots would collide with the arrows. */
@media (max-width: 380px){
  .hero-dot{width:18px}
  .hero-dots{gap:.45rem}
}

/* Portrait tablets get the split too, but not the phone's full-bleed copy:
   stretching the buttons to 753px reads as a mistake, and the lede hits ~90
   characters a line. Buttons go back side by side and the measure is capped. */
@media (orientation: portrait) and (min-width: 600px) and (max-width: 1024px){
  .hero-box{padding:1.8rem 2rem 2rem}
  .hero-slider .lede{max-width:62ch;font-size:1.02rem}
  .hero-ctas{flex-direction:row;flex-wrap:wrap;gap:.9rem}
  .hero-ctas>a{width:auto;min-width:210px}
}

/* Short phones (iPhone SE 375×667, 320×690): the band is fine, it's the copy
   stack that runs the CTAs past the fold. Compress the vertical rhythm rather
   than crop the photo. */
@media (orientation: portrait) and (max-width: 560px) and (max-height: 740px){
  .hero-slide{padding-bottom:4rem}
  .hero-box{padding:1.1rem 1.15rem 1.15rem}
  .hero-slider h1{font-size:clamp(1.9rem,8vw,2.5rem)}
  .hero-slider .lede{font-size:.9rem;margin:.7rem 0 1rem}
  .hero-eyebrow{margin-bottom:.7rem}
  .hero-ctas>a{padding:.7rem 1.4rem}
}

/* Phones and small tablets in LANDSCAPE: wide enough that the photo crops
   sanely, so the overlay is right — but far too short for the desktop hero's
   6rem/5rem padding and centre-height arrows. Tightened overlay. */
@media (orientation: landscape) and (max-height: 620px){
  /* Content-driven, not 86svh: on a 390px-tall landscape phone the nav alone
     eats 107px, so a viewport-proportional hero guarantees the CTAs fall off. */
  .hero-slider,.hero-track,.hero-slide{min-height:0}
  /* Bottom padding clears the control row (44px + 2×1rem). */
  .hero-slide{padding:2.5rem 1.5rem 4.75rem}
  .hero-box{max-width:min(100%,520px);padding:1.2rem 1.5rem}
  .hero-slider h1{font-size:clamp(1.9rem,4.5vw,2.6rem)}
  .hero-slider .lede{font-size:.92rem;margin:.7rem 0 1.1rem;max-width:56ch}
  .hero-eyebrow{margin-bottom:.7rem}
  .hero-ctas>a{padding:.65rem 1.2rem}
  .hero-arrow{top:auto;bottom:1rem;transform:none;width:44px;height:44px}
  .hero-arrow.prev{left:1.5rem}
  .hero-arrow.next{right:1.5rem}
  .hero-dots{bottom:2.2rem}
}

/* Wide screens: run the whole headline on one line and let the box grow to suit.
   The break comes from a newline in the `heading` field (nl2br → <br>); hiding
   the <br> collapses it to the space nl2br leaves behind. Deliberately NOT
   white-space:nowrap — headings are admin-editable, and a long one would then
   punch out of the box instead of wrapping. */
@media (min-width: 1032px){
  .hero-slider h1 br{display:none}
  .hero-slider h1{max-width:none}
  .hero-box{max-width:min(100%,920px)}
  /* The box is ~780px wide now, so the old 46ch measure left the lede at three
     lines and it, not the headline, set the box height. 78ch takes it to two
     while staying inside a comfortable reading measure. */
  .hero-slider .lede{max-width:78ch}
}

/* Marquee */
.marquee{background:var(--pink);overflow:hidden;border-top:1px solid var(--bg);border-bottom:1px solid var(--bg);position:relative;z-index:3}
/* Seamless: spacing lives on each item (margin-right) so a -50% shift lands
   exactly on one duplicated tile — no half-gap jump at the loop restart. */
.marquee-track{display:flex;width:max-content;animation:scroll-x 34s linear infinite;padding:.9rem 0;white-space:nowrap;will-change:transform}
.marquee-item{font-family:var(--font-display);font-size:1.1rem;letter-spacing:.18em;text-transform:uppercase;color:#fff;display:flex;align-items:center;gap:2.5rem;margin-right:2.5rem}
.marquee-item::after{content:"";display:inline-block;width:10px;height:10px;background:#fff;border-radius:50%;flex-shrink:0}
@keyframes scroll-x{from{transform:translate3d(0,0,0)}to{transform:translate3d(-50%,0,0)}}
@media (prefers-reduced-motion: reduce){.marquee-track{animation:none}}

/* Stats */
.stats{padding:3.5rem 2rem;border-bottom:1px solid var(--line);background:var(--bg)}
/* Flex and centred, not the original fixed 4-column grid. That grid was written
   for four stats; at two (2026-08-08) the pair sat in columns 1-2 and stranded the
   right half of the band, reading as content that failed to load. Centring a short
   pair is what makes the empty space look chosen. It also scales: a third or fourth
   stat just joins the row, so this does not need revisiting if the copy grows. */
.stats-grid{max-width:var(--maxw);margin:0 auto;display:flex;flex-wrap:wrap;
  justify-content:center;align-items:center;row-gap:2.5rem}
.stat{text-align:center;padding:0 clamp(2rem,5vw,5rem)}
/* Hairline between adjacent stats. On the element rather than a gap pseudo-element
   so it cannot drift out of position when the row wraps. */
.stat + .stat{border-left:1px solid var(--line)}
.stat .n{font-family:var(--font-display);font-size:clamp(2.8rem,6vw,5rem);color:var(--amber);line-height:1;letter-spacing:-.02em}
.stat .l{font-family:var(--font-stencil);font-size:.78rem;letter-spacing:.22em;text-transform:uppercase;color:var(--muted);margin-top:.5rem}

/* Section base */
section{position:relative}
.container-x{max-width:var(--maxw);margin:0 auto;padding:0 2rem}
.section-head{display:flex;align-items:flex-end;justify-content:space-between;gap:2rem;margin-bottom:3rem;flex-wrap:wrap}
.section-head .num{font-family:var(--font-stencil);font-weight:700;font-size:1rem;color:var(--gold);letter-spacing:.3em;margin-bottom:.6rem;display:block}
.section-head h2{font-size:clamp(2.4rem,5.5vw,4.6rem)}
.section-head .link{font-family:var(--font-body);font-weight:800;font-size:.8rem;letter-spacing:.2em;text-transform:uppercase;color:var(--ink);border-bottom:2px solid var(--pink);padding-bottom:.25rem;align-self:flex-end}

/* Plate divider */
.plate{height:14px;background-image:
  radial-gradient(circle at 4px 4px, var(--panel-2) 1.5px, transparent 1.8px),
  radial-gradient(circle at 10px 10px, var(--panel-2) 1.5px, transparent 1.8px);
  background-size:12px 12px;background-color:var(--panel);border-top:1px solid var(--line);border-bottom:1px solid var(--line)}

/* Featured Builds / gallery cards */
.builds{padding:6rem 0;background:var(--bg)}
.builds-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:1.2rem}
.build-card{position:relative;overflow:hidden;background:var(--panel);aspect-ratio:3/4;display:flex;flex-direction:column}
.build-card img{width:100%;height:100%;object-fit:cover;transition:transform .5s}
.build-card:hover img{transform:scale(1.04)}
/* Only overlay: the bottom caption strip where the text sits */
.build-card-overlay{position:absolute;inset:auto 0 0 0;padding:1.2rem 1.3rem;background:linear-gradient(180deg,transparent 0%,rgba(11,10,12,.95) 85%);z-index:2}
.build-card .tag{font-family:var(--font-stencil);font-size:.7rem;letter-spacing:.25em;color:var(--gold);text-transform:uppercase;margin-bottom:.4rem;display:block}
.build-card h2{font-size:1.5rem;color:var(--ink)}
/* Sits on the raw photo, above the bottom scrim, and measured 2.36:1 against a
   bright sky. It is a card ordinal — pure decoration under 1.4.3, and now
   aria-hidden so it isn't announced as a meaningless "01" — but "exempt" is not
   the same as "legible", and a shadow halo costs nothing and holds up over any
   photo the CMS throws at it. */
.build-card .num-corner{position:absolute;top:.9rem;left:1rem;font-family:var(--font-stencil);font-size:.8rem;color:var(--ink);letter-spacing:.2em;z-index:2;opacity:.85;text-shadow:0 1px 2px rgba(0,0,0,.95),0 0 8px rgba(0,0,0,.8)}
.build-card-overlay::before{content:"";position:absolute;left:0;right:0;top:0;height:3px;background:var(--pink)}

/* --- Shop category filters --------------------------------------------------
   These used to borrow .nav-menu, whose colour (#17110A) exists to sit on the
   GOLD nav bar — on the dark shop page that rendered near-black on near-black
   and the filters were effectively invisible. Gold on --bg instead, which is
   the same pairing the nav uses, just inverted. Active is white with the red
   underline so the current category still reads as the selected one. */
.shop-filters{display:flex;flex-wrap:wrap;gap:1.4rem;list-style:none;padding:0;margin:0 0 2.5rem}
.shop-filters a{display:inline-block;font-family:var(--font-body);font-weight:700;font-size:.82rem;letter-spacing:.18em;text-transform:uppercase;color:var(--gold);border-bottom:2px solid transparent;padding-bottom:3px;transition:color .15s,border-color .15s}
.shop-filters a:hover{color:var(--ink);border-bottom-color:var(--gold-2)}
.shop-filters a.active{color:var(--ink);border-bottom-color:var(--red)}
.shop-filters a:focus-visible{outline:2px solid var(--gold);outline-offset:3px}

/* --- Per-letter heading reveal ----------------------------------------------
   Paired with js/typewriter.js, which wraps the characters already in the
   markup. No cursor, per the brief — letters just arrive.

   steps(1,end) rather than a fade: a typewriter puts a letter down, it doesn't
   dissolve one in. The spans stay `inline` (no inline-block) so the heading is
   still one run of text for screen readers and line-breaking is unchanged.

   .tw-run is added by JS, so with JS off nothing here applies and the heading
   renders as ordinary text — the opacity:0 can never strand a heading.

   FAIL-SAFE BY CONSTRUCTION. This was first written with `animation: … forwards`
   and letters got STUCK INVISIBLE — on /builds, "Custom builds" rendered as
   "CUS  M BUILDS" with characters 4 and 5 permanently at opacity 0 even though
   the Web Animations API reported both animations `finished` with fill
   `forwards`. The held fill value simply never got committed for those two.

   So the resting state is no longer an animation's held value, it is a real
   declaration: `.tw-go .tw-c{opacity:1}`. The reveal is a transition INTO that
   state. If a transition is dropped, mis-timed or never fires, the letter
   still ends up at opacity 1 — the worst case is that it appears instantly
   instead of on cue. There is no longer any failure mode where a heading stays
   invisible, which is the only outcome that actually matters on an <h1>.

   .tw-done is the belt-and-braces: JS sets it once the run should be over and
   it pins every letter visible with transitions off. */
h1.tw-run .tw-c{opacity:0;transition:opacity .01s steps(1,end);transition-delay:var(--d,0ms)}
h1.tw-run.tw-go .tw-c{opacity:1}
h1.tw-run.tw-done .tw-c{opacity:1;transition:none}
@media (prefers-reduced-motion: reduce){
  h1.tw-run .tw-c{opacity:1;transition:none}
}

/* --- Perforated plate edge (global section divider) -------------------------
   The punched-metal strip that used to be baked into .story::before. Promoted
   to a utility class so any band can carry it (client, 2026-08-09: "i like
   that design as a global section devider"). Add class="perf-edge" to a
   <section> — it supplies its own position:relative, so it works on a section
   that isn't already positioned.

   The dot colour is --panel-2, which sits a step above BOTH surface tokens
   (--bg #0B0A0C and --panel #15131A), so one value reads on every band. A
   section on some future surface can override --perf-dot rather than
   redeclaring the gradients. aria-hidden by nature (a ::before with no text)
   and pointer-events:none so it can never eat a click near a section edge. */
.perf-edge{position:relative}
.perf-edge::before{content:"";position:absolute;top:0;left:0;right:0;height:14px;pointer-events:none;background-image:
  radial-gradient(circle at 4px 4px, var(--perf-dot, var(--panel-2)) 1.5px, transparent 1.8px),
  radial-gradient(circle at 10px 10px, var(--perf-dot, var(--panel-2)) 1.5px, transparent 1.8px);
  background-size:12px 12px}

/* About / Shop story */
.story{padding:7rem 0;background:var(--panel);position:relative}
.story-grid{display:grid;grid-template-columns:.9fr 1.1fr;gap:5rem;align-items:center}
/* A single landscape photo is much shorter than the text beside it, and
   centring left it floating with ~160px of dead space above — it read as a
   mistake. Top-aligning lines the photo up with the "Who we are" eyebrow.
   Scoped with :has(> img) so it applies ONLY to the single-photo case: the home
   page's 2x2 tile grid is nearly as tall as its text and still centres. */
.story-grid:has(.story-img > img){align-items:start}
.story-img{position:relative}
/* The single story photo keeps its OWN aspect ratio.
   This used to be a hard aspect-ratio:4/5 with object-fit:cover, chosen to suit
   shop-tools-bench.webp (1600x1999, which is exactly 4:5). The moment a
   different photo went in — the About page's 800x533 brackets shot — the crop
   squeezed a 1.50 image into 1.03 and cut away a third of its width.
   These photos get swapped by hand and from the admin, so the layout has to
   accept whatever shape arrives rather than assume one. height:auto + no
   aspect-ratio means the image is never cropped, whatever its proportions.
   (.story-tiles img below sets its own 4/3 for the home page grid, and wins on
   source order.) */
.story-img img{width:100%;height:auto;object-fit:cover;filter:brightness(.7) contrast(1.15) saturate(.85)}

/* Featured-products tile grid in the story slot. Four 4:3 product crops fill
   the same portrait block the single photo used to, which is the only way to
   show catalogue photos here without a brutal centre-crop: every product shot
   is LANDSCAPE by standing policy and the slot is 4:5. Each cell keeps its
   own aspect, the block as a whole stays portrait. The lighter filter than
   the stock photo's is deliberate — these are parts being sold, not mood. */
.story-tiles{display:grid;grid-template-columns:1fr 1fr;gap:.7rem;width:100%}
/* With a single photo the "20+ years" stamp hangs over a dead corner of the
   image, which is fine. Over the tile grid it landed on the fourth tile and
   buried its name plate — so in tile mode the stamp stops being absolute and
   sits under the grid instead, still tucked to the right edge. */
.story-img:has(.story-tiles){display:flex;flex-direction:column;align-items:flex-end}
.story-img:has(.story-tiles) .stamp{position:static;margin:.9rem -1rem 0 0}
.story-tiles a{display:block;position:relative;overflow:hidden;background:var(--panel-2)}
/* height:auto is load-bearing, not tidiness. The width/height attributes on
   the <img> are presentational hints, so with only `width` set in CSS the
   height attribute (675) stayed definite and aspect-ratio was ignored —
   every tile rendered 675px tall. */
.story-tiles img{width:100%;height:auto;aspect-ratio:4/3;object-fit:cover;filter:brightness(.82) contrast(1.08) saturate(.92);transition:transform .35s ease,filter .35s ease}
.story-tiles a:hover img,.story-tiles a:focus-visible img{transform:scale(1.05);filter:brightness(1) contrast(1.05) saturate(1)}
.story-tiles a:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
/* Name plate over each tile — the point is to show WHAT the part is, not just
   that a photo exists. Gradient rather than a flat bar so the photo still
   reads through it. */
/* Measured 2026-08-11: with the old `to top, rgba(0,0,0,.88), transparent`
   ramp, a two-line tile name put its FIRST line where the gradient had already
   thinned out. Against the brightest photo in the set that came to 4.06:1 —
   under the 4.5 bar for 12px text. The others cleared it by roughly 1.2, which
   is the real problem: these photos come from the CMS, so a scrim that only
   just passes today fails the next time David uploads something brighter.
   The stops are in REM, not percentages, and that is the whole point: a
   percentage ramp stretches with the element, so a three-line product name
   pushed its first line up into the faded part and dropped to 2.6:1 while
   two-line names passed. Anchoring the fade to the 1.6rem top padding means the
   text area is always on .94 regardless of how long a name the admin types. */
.story-tiles .tile-name{position:absolute;left:0;right:0;bottom:0;padding:1.6rem .7rem .55rem;font-family:var(--font-body);font-weight:700;font-size:.72rem;letter-spacing:.04em;line-height:1.25;color:#fff;background:linear-gradient(to top,rgba(0,0,0,.94) 0,rgba(0,0,0,.94) calc(100% - 1.6rem),rgba(0,0,0,.5) calc(100% - .5rem),transparent 100%)}
@media (max-width: 520px){
  .story-tiles .tile-name{font-size:.66rem;padding:1.2rem .5rem .45rem}
}
.story-img .stamp{position:absolute;bottom:-1.3rem;right:-1rem;background:var(--pink);color:#fff;padding:1rem 1.4rem;font-family:var(--font-display);font-size:1.1rem;letter-spacing:.1em;text-transform:uppercase;clip-path:polygon(0 0,calc(100% - 12px) 0,100% 12px,100% 100%,12px 100%,0 calc(100% - 12px));text-align:center;line-height:1}
.story-img .stamp .big{font-size:2.6rem;display:block;line-height:.9;margin-bottom:.25rem}
.story h2{font-size:clamp(2.6rem,5.5vw,4.8rem);margin-bottom:1.5rem}
.story p{color:var(--muted);font-size:1.05rem;margin-bottom:2rem;max-width:55ch}
.story-list{list-style:none;padding:0;margin:0 0 2rem 0}
.story-list li{display:flex;gap:1rem;align-items:flex-start;padding:.85rem 0;border-bottom:1px solid var(--line)}
.story-list li::before{content:"";flex-shrink:0;width:18px;height:18px;margin-top:.3rem;background:var(--pink);clip-path:polygon(0 50%, 35% 100%, 100% 0)}
.story-list li span{font-family:var(--font-body);font-weight:600;font-size:1rem;color:var(--ink)}

/* Services */
.services{padding:7rem 0;background:var(--bg)}
.services-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1.2rem}
/* Flex column + stretched grid cells so .svc-foot can pin to the bottom and the
   badges/links align across a row even when the body copy runs to different lengths. */
.svc{background:var(--panel);padding:2.2rem 2rem;border:1px solid var(--line);position:relative;transition:border-color .2s, transform .2s;display:flex;flex-direction:column;height:100%}
.svc:hover{border-color:var(--gold);transform:translateY(-3px)}
.svc::before{content:"";position:absolute;top:.6rem;left:.6rem;width:6px;height:6px;background:var(--pink);border-radius:50%}
.svc::after{content:"";position:absolute;top:.6rem;right:.6rem;width:6px;height:6px;background:var(--pink);border-radius:50%}
.svc .num{font-family:var(--font-stencil);font-size:.75rem;letter-spacing:.25em;color:var(--amber);margin-bottom:1rem;display:block}
.svc h3{font-size:1.65rem;margin-bottom:.75rem}
.svc p{color:var(--muted);font-size:.96rem;margin:0;line-height:1.55}
/* How a service is sold: "Kits available" vs quote-only (frame stretching).
   margin-top:auto pushes this block to the bottom of the flex card. */
.svc-foot{margin-top:auto;padding-top:1.5rem}
.svc-tag{display:inline-block;font-family:var(--font-body);font-size:.68rem;font-weight:800;letter-spacing:.16em;text-transform:uppercase;color:var(--gold);border:1px solid var(--gold);padding:.3rem .6rem}
.svc-tag-quote{color:var(--red-text);border-color:var(--red-text)}
.svc-link{display:flex;align-items:center;min-height:24px;margin-top:.9rem;font-family:var(--font-body);font-size:.78rem;font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:var(--ink);border-bottom:1px solid transparent;width:fit-content}
.svc-link:hover{color:var(--gold);border-bottom-color:var(--gold)}

/* Brands We Build */
.brands{padding:7rem 0;background:var(--panel);border-top:1px solid var(--line)}
.brands-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:1.2rem}
.brand-card{background:var(--bg);padding:2.4rem 2rem 2.2rem;border:1px solid var(--line);position:relative;transition:border-color .2s, transform .2s}
.brand-card:hover{border-color:var(--gold);transform:translateY(-3px)}
.brand-card::before{content:"";position:absolute;top:0;left:0;right:0;height:4px;background:var(--pink)}
.brand-name{font-family:var(--font-display);font-weight:400;font-size:1.9rem;color:var(--ink);text-transform:uppercase;letter-spacing:-.005em;line-height:.95;margin:0 0 1.3rem 0;padding-bottom:1rem;border-bottom:1px dashed var(--line)}
.brand-models{list-style:none;padding:0;margin:0}
.brand-models li{padding:.45rem 0;font-family:var(--font-stencil);font-weight:700;font-size:.95rem;letter-spacing:.18em;text-transform:uppercase;color:var(--muted);transition:color .15s;border-bottom:1px dashed var(--line)}
.brand-models li:last-child{border-bottom:0}
.brand-card:hover .brand-models li{color:var(--ink)}
.brand-card .all{font-family:var(--font-stencil);font-weight:700;font-size:.95rem;letter-spacing:.18em;text-transform:uppercase;color:var(--amber);padding:.45rem 0}

/* Process */
.process{padding:7rem 0;background:var(--panel)}
.process-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:0;border-top:1px solid var(--line);border-left:1px solid var(--line)}
.step{padding:2.5rem 1.8rem;border-right:1px solid var(--line);border-bottom:1px solid var(--line);position:relative}
.step .num{font-family:var(--font-stencil);font-weight:700;font-size:4.5rem;color:var(--panel-2);line-height:.9;display:block;margin-bottom:1rem}
.step h3{font-size:1.3rem;margin-bottom:.7rem}
.step p{color:var(--muted);font-size:.92rem;margin:0;line-height:1.55}

/* Testimonials */
.quotes{padding:7rem 0;background:var(--bg)}
.quotes-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1.4rem}
.q{padding:2.2rem;background:var(--panel);border-left:3px solid var(--pink);position:relative}
.q::before{content:"\201C";position:absolute;top:.4rem;left:1rem;font-family:var(--font-display);font-size:5rem;color:var(--gold);opacity:.25;line-height:1}
.q .body{font-family:var(--font-body);font-size:1.05rem;font-weight:500;color:var(--ink);margin:0 0 1.4rem 0;line-height:1.55;position:relative;z-index:1}
.q .who{font-family:var(--font-stencil);font-size:.78rem;letter-spacing:.18em;color:var(--muted);text-transform:uppercase}
.q .who strong{color:var(--ink);font-weight:700}

/* Big CTA */
.cta{padding:9rem 2rem 5rem;background:var(--bg);position:relative;overflow:hidden;text-align:left;border-top:1px solid var(--line)}
.cta-bg-word{
  position:absolute;left:-2vw;bottom:-3vw;font-family:var(--font-display);font-size:32vw;color:transparent;
  -webkit-text-stroke:2px var(--panel-2);line-height:.85;letter-spacing:-.04em;pointer-events:none;user-select:none;z-index:0;text-transform:uppercase
}
.cta-inner{position:relative;z-index:2;max-width:var(--maxw);margin:0 auto;display:grid;grid-template-columns:1.1fr .9fr;gap:4rem;align-items:flex-end}
.cta h2{font-size:clamp(3rem,8vw,8rem)}
.cta .lede{color:var(--muted);font-size:1.1rem;max-width:50ch;margin:1.5rem 0}
.cta .contact-block{padding:2.2rem;background:var(--panel);border:1px solid var(--line)}
.cta .contact-block .item{padding:1rem 0;border-bottom:1px solid var(--line)}
.cta .contact-block .item:last-of-type{border-bottom:0}
.cta .contact-block .lab{font-family:var(--font-stencil);font-size:.74rem;letter-spacing:.22em;color:var(--gold);text-transform:uppercase;margin-bottom:.3rem;display:block}
.cta .contact-block .val{font-family:var(--font-display);font-size:1.7rem;color:var(--ink);text-transform:uppercase;letter-spacing:.01em}
.cta .contact-block .btn-pink{margin-top:1.4rem;width:100%;justify-content:center}

/* ---- Sub-page shell (builds gallery, shop, cms pages) ---- */
.page-hero{padding:5rem 0 3rem;background:var(--panel);border-bottom:1px solid var(--line);position:relative}
.page-hero .container-x{position:relative;z-index:1}
.page-hero h1{font-size:clamp(2.8rem,7vw,6rem)}
.page-hero .lede{color:var(--muted);font-size:1.05rem;max-width:60ch;margin-top:1.2rem}
.page-body{padding:5rem 0;background:var(--bg)}
.prose{max-width:70ch}
.prose p{color:var(--muted);font-size:1.05rem;margin:0 0 1.3rem}
.prose h2{font-size:2rem;margin:2.5rem 0 1rem}
.prose a{color:var(--gold)}

/* WCAG 1.4.1 (Use of Color) — a link sitting inside a sentence must be
   distinguishable WITHOUT colour. Gold on dark clears the contrast bar easily,
   but that only helps someone who can separate gold from grey; colour alone is
   not a cue. So in-text links are underlined.
   Scoped to running-text contexts on purpose: nav items, footer lists, product
   cards and buttons are standalone targets, not links embedded in prose, and
   underlining those would be noise. `.cookie-bar a` already did this — the rest
   of the site now matches it. */
.prose a,
.lede a,
.pd-install-note a,
.policy-gate a,
.cs-note a,
.form-note a{text-decoration:underline;text-underline-offset:.18em;text-decoration-thickness:1px}

/* A button that happens to live inside .prose keeps its own colours.
   `.prose a` is more specific than `.btn-pink`, so without this it repaints
   white button text gold — gold on the red fill fails 1.4.3, which is exactly
   what axe caught on the checkout success page. */
.prose a.btn-pink,
.prose a.btn-ghost{text-decoration:none}
.prose a.btn-pink{color:#fff}
.prose a.btn-ghost{color:var(--ink)}
.prose a.btn-ghost:hover{color:var(--bg)}
.crumbs{font-family:var(--font-stencil);font-size:.75rem;letter-spacing:.2em;text-transform:uppercase;color:var(--muted);margin-bottom:1rem}
.crumbs a{display:inline-flex;align-items:center;min-height:24px;color:var(--muted)}.crumbs a:hover{color:var(--gold)}

/* ---- Shop / product grid ---- */
.shop-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1.2rem}

/* Shop "All" view: one section per category. The page-level .section-head h2
   runs up to 4.6rem — too loud repeated five times down one page, so sections
   use a tighter scale and a slimmer bottom margin. */
.shop-section{margin-bottom:4.5rem}
.shop-section:last-child{margin-bottom:0}
/* A category carrying the perforated divider needs room above its heading, or
   the strip sits on top of the "// 02" eyebrow. The extra margin is what makes
   the strip read as a break BETWEEN categories rather than as decoration
   attached to the one below it. */
.shop-section.perf-edge{margin-top:1.5rem;padding-top:3rem}
.shop-section .section-head{margin-bottom:1.8rem}
.shop-section .section-head h2{font-size:clamp(1.9rem,3.5vw,2.8rem)}
.shop-section-lede{color:var(--muted);font-size:.98rem;max-width:64ch;margin-top:.5rem}
.product-card{background:var(--panel);border:1px solid var(--line);position:relative;display:flex;flex-direction:column;transition:border-color .2s, transform .2s}
.product-card:hover{border-color:var(--gold);transform:translateY(-3px)}
.product-card .thumb{aspect-ratio:4/3;overflow:hidden;background:var(--panel-2)}
.product-card .thumb img{width:100%;height:100%;object-fit:cover}
.product-card .pc-body{padding:1.3rem 1.4rem 1.5rem}
.product-card .cat{font-family:var(--font-stencil);font-size:.68rem;letter-spacing:.24em;text-transform:uppercase;color:var(--gold);display:block;margin-bottom:.5rem}
.product-card h3{font-size:1.25rem;color:var(--ink);margin-bottom:.6rem;font-family:var(--font-display)}
.product-card .price{font-family:var(--font-display);font-size:1.5rem;color:var(--amber)}
.product-card .price .was{font-family:var(--font-body);font-size:.95rem;color:var(--muted);text-decoration:line-through;margin-left:.5rem}
.product-card .price .mto{font-family:var(--font-stencil);font-size:.7rem;letter-spacing:.2em;color:var(--muted);text-transform:uppercase}

/* ---- Product detail ---- */
.pd-grid{display:grid;grid-template-columns:1.1fr .9fr;gap:3.5rem;align-items:start}
.pd-gallery img{width:100%;aspect-ratio:4/3;object-fit:cover;border:1px solid var(--line)}

/* Gallery controls — thumbs swap the main slot, main slot opens the lightbox */
.pd-main{display:block;width:100%;padding:0;border:0;background:none;cursor:zoom-in;position:relative}
/* Play badge — marks a video in the gallery. A ::after triangle rather than an
   icon file keeps it one less request and it inherits the accent colour. */
.pd-main.is-video,.pd-thumb.is-video{cursor:pointer}
.pd-main.is-video::after,.pd-thumb.is-video::after{content:'';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);border-style:solid;border-color:transparent transparent transparent #fff;filter:drop-shadow(0 2px 6px rgba(0,0,0,.6))}
.pd-main.is-video::before,.pd-thumb.is-video::before{content:'';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);border-radius:50%;background:rgba(6,5,9,.55);border:2px solid rgba(255,255,255,.9)}
.pd-main.is-video::before{width:76px;height:76px}
.pd-main.is-video::after{border-width:15px 0 15px 24px;margin-left:5px}
.pd-thumb{position:relative}
.pd-thumb.is-video::before{width:34px;height:34px;border-width:1.5px}
.pd-thumb.is-video::after{border-width:7px 0 7px 11px;margin-left:2px}
.pd-thumbs{display:grid;grid-template-columns:repeat(4,1fr);gap:1rem;margin-top:1rem}
.pd-thumb{padding:0;border:0;background:none;cursor:pointer;opacity:.55;transition:opacity .15s}
.pd-thumb:hover{opacity:.85}
.pd-thumb.is-active{opacity:1}
.pd-thumb.is-active img{border-color:var(--gold)}

/* Lightbox: a fullscreen transparent <dialog>; clicks on the dialog surface
   itself (outside the image/buttons) close it. The img rules override the
   generic .pd-gallery img crop above — the whole point of the lightbox is
   the uncropped master. */
.pd-lightbox{width:100vw;height:100dvh;max-width:none;max-height:none;margin:0;padding:0;border:0;background:transparent;display:none;align-items:center;justify-content:center;opacity:0;transform:scale(.965);transition:opacity .22s ease,transform .22s ease,display .22s allow-discrete,overlay .22s allow-discrete}
.pd-lightbox[open]{display:flex;opacity:1;transform:scale(1)}
/* @starting-style gives the OPEN transition a from-state; the allow-discrete
   entries above keep display/top-layer alive through the CLOSE fade. Browsers
   without these just snap open/closed — the old behavior, not a break. */
@starting-style{.pd-lightbox[open]{opacity:0;transform:scale(.965)}}
.pd-lightbox::backdrop{background:rgba(6,5,9,.94);opacity:0;transition:opacity .22s ease,display .22s allow-discrete,overlay .22s allow-discrete}
.pd-lightbox[open]::backdrop{opacity:1}
@starting-style{.pd-lightbox[open]::backdrop{opacity:0}}
.pd-lightbox img{width:auto;max-width:calc(100vw - 2rem);height:auto;max-height:calc(100dvh - 2rem);aspect-ratio:auto;object-fit:contain;border:0;transition:opacity .18s ease}
.pd-lightbox img.is-loading{opacity:.15}
/* WebGL layer: the same self-displacement transition as the home-page hero.
   It only appears once three.js has lazily loaded (.gl-active), so the <img>
   carries the first paint and there is never a blank frame. */
.pd-lb-gl{position:absolute;inset:0;width:100%;height:100%;display:block}
.pd-lightbox:not(.gl-active) .pd-lb-gl{display:none}
.pd-lightbox.gl-active img{visibility:hidden}

/* Video player — takes over the frame while a clip is showing. */
.pd-lb-player{position:relative;width:min(1400px,calc(100vw - 2rem));aspect-ratio:16/9;max-height:calc(100dvh - 2rem);background:#000}
.pd-lb-player iframe,.pd-lb-player video{position:absolute;inset:0;width:100%;height:100%;border:0;object-fit:contain;background:#000}
.pd-lightbox.is-playing .pd-lb-gl,.pd-lightbox.is-playing>img{display:none}
/* The arrows sit over a 16:9 player on desktop; on phones the player is short
   and they'd float in empty space, so drop them to the bottom corners. */
@media (max-width:720px){
  .pd-lightbox.is-playing .pd-lb-arrow{top:auto;bottom:1rem;transform:none}
}
@media (prefers-reduced-motion: reduce){
  .pd-lightbox,.pd-lightbox::backdrop,.pd-lightbox img{transition:none}
}
.pd-lightbox button{position:absolute;display:flex;align-items:center;justify-content:center;width:52px;height:52px;padding:0;border:1px solid var(--line);background:rgba(6,5,9,.7);color:var(--ink);font-size:1.9rem;line-height:1;cursor:pointer}
.pd-lightbox button:hover{border-color:var(--gold);color:var(--gold)}
.pd-lb-close{top:1rem;right:1rem}
.pd-lb-arrow{top:50%;transform:translateY(-50%)}
.pd-lb-arrow.prev{left:1rem}
.pd-lb-arrow.next{right:1rem}
.pd-lb-counter{position:absolute;bottom:1.1rem;left:50%;transform:translateX(-50%);font-family:var(--font-stencil);font-size:.8rem;letter-spacing:.25em;color:var(--muted)}
.pd-info h1{font-size:clamp(2rem,4vw,3.2rem);margin-bottom:1rem}
.pd-info .price{font-family:var(--font-display);font-size:2.4rem;color:var(--amber);margin:1rem 0}
.pd-info .price .was{font-family:var(--font-body);font-size:1.1rem;color:var(--muted);text-decoration:line-through;margin-left:.6rem}
.pd-meta{list-style:none;padding:0;margin:1.5rem 0;border-top:1px solid var(--line)}
.pd-meta li{display:flex;justify-content:space-between;gap:1rem;padding:.8rem 0;border-bottom:1px solid var(--line);font-size:.95rem}
.pd-meta li .k{font-family:var(--font-stencil);font-size:.72rem;letter-spacing:.2em;text-transform:uppercase;color:var(--muted)}
.pd-meta li .v{color:var(--ink);font-weight:600}

/* Add-on options — priced extras, selectable, above the buy row.
   A fieldset rather than a list: these are form controls now, and the legend
   is what a screen reader announces before the group. */
.pd-options{margin:1.8rem 0;border:1px solid var(--line);padding:1.4rem 1.5rem}
.pd-options legend{font-family:var(--font-stencil);font-size:.78rem;letter-spacing:.22em;text-transform:uppercase;color:var(--muted);padding:0 .5rem;margin-left:-.5rem}
.opt-row{display:grid;grid-template-columns:auto 1fr auto;align-items:baseline;gap:.85rem;padding:.7rem 0;border-top:1px solid var(--line);font-size:.95rem;cursor:pointer}
.pd-options .opt-row:first-of-type{border-top:0}
.opt-row input[type=checkbox]{width:1.05rem;height:1.05rem;accent-color:var(--amber);cursor:pointer;transform:translateY(.15rem)}
.opt-row:hover .opt-label{color:var(--amber)}
.opt-label{color:var(--ink);font-weight:600;transition:color .15s}
.opt-note{display:block;font-weight:400;font-size:.82rem;color:var(--muted);margin-top:.15rem}
.opt-price{font-family:var(--font-display);font-size:1.15rem;color:var(--amber);white-space:nowrap}

/* Buy row — quantity + add to basket */
.pd-buy{margin-top:1.5rem}
.pd-buy-row{display:flex;gap:1rem;align-items:stretch;flex-wrap:wrap}
.qty-field{display:flex;align-items:center;gap:.6rem;border:1px solid var(--line);padding:0 .9rem}
.qty-label{font-family:var(--font-stencil);font-size:.7rem;letter-spacing:.2em;text-transform:uppercase;color:var(--muted)}
.qty-field input{width:3.4rem;background:transparent;border:0;color:var(--ink);font-family:var(--font-display);font-size:1.1rem;padding:.7rem 0;text-align:center}
.qty-field input:focus{outline:2px solid var(--amber);outline-offset:2px}
.pd-buy-row .btn-pink{flex:1;min-width:14rem;border:0;cursor:pointer;font:inherit}
.pd-ask{display:inline-block;margin-top:1rem}
.pd-install-note{margin:1.2rem 0 0;font-size:.9rem;color:var(--muted);max-width:44ch}
.pd-install-note a{color:var(--gold)}

/* ---- Basket ---- */
.cart-grid{display:grid;grid-template-columns:1fr 22rem;gap:3rem;align-items:start}
.cart-line{display:grid;grid-template-columns:7rem 1fr auto;gap:1.4rem;padding:1.6rem 0;border-bottom:1px solid var(--line)}
.cart-line:first-child{border-top:1px solid var(--line)}
.cl-thumb img{width:7rem;height:5.25rem;object-fit:cover;display:block}
.cl-body h2{font-size:1.15rem;margin:0 0 .5rem}
.cl-body h2 a{color:var(--ink);text-decoration:none}
.cl-body h2 a:hover{color:var(--amber)}
.cl-options{list-style:none;padding:0;margin:.4rem 0 0;font-size:.88rem}
.cl-options li{display:flex;justify-content:space-between;gap:1rem;padding:.2rem 0;color:var(--muted);max-width:26rem}
.cl-options .opt-price{font-size:.95rem}
.cl-lead{margin:.6rem 0 0;font-size:.82rem;color:var(--muted)}
/* Qty / Update / Remove sit on one line. The two controls live in SEPARATE
   forms (a PATCH and a DELETE), so they are independent flex items whose
   natural heights differ — the qty form is as tall as its input, the remove
   form only as tall as its text. align-items:center alone left Update and
   Remove a couple of pixels apart. Giving every control the same box height
   makes them share a centre line regardless of which form they belong to. */
.cl-controls{display:flex;align-items:center;gap:1.4rem;margin-top:.9rem;flex-wrap:wrap}
.cl-controls form{display:flex;align-items:center;gap:.6rem;margin:0}
.cl-qty{display:flex;align-items:center;gap:.6rem}
.cl-qty label{display:flex;align-items:center;gap:.5rem}
.cl-qty input{width:3.2rem;background:transparent;border:1px solid var(--line);color:var(--ink);padding:.4rem;text-align:center;font-family:var(--font-display);line-height:1.2}
/* min-height matches the qty input's box (padding + border + line-height) so
   the text links centre against it, and clears the WCAG 2.5.8 24px target. */
.link-btn{display:inline-flex;align-items:center;min-height:2.15rem;background:none;border:0;padding:0;color:var(--muted);font-size:.82rem;letter-spacing:.06em;text-transform:uppercase;cursor:pointer;text-decoration:underline;font-family:var(--font-stencil)}
.link-btn:hover{color:var(--amber)}
.link-btn.danger:hover{color:#e0533f}
.cl-price{text-align:right;white-space:nowrap}
.cl-total{font-family:var(--font-display);font-size:1.35rem;color:var(--ink)}
.cl-each{font-size:.78rem;color:var(--muted);margin-top:.2rem}

.cart-summary{border:1px solid var(--line);padding:1.6rem;position:sticky;top:1.5rem}
.cart-summary h2{font-family:var(--font-stencil);font-size:.78rem;letter-spacing:.22em;text-transform:uppercase;color:var(--muted);margin:0 0 1.2rem}
.cs-row{display:flex;justify-content:space-between;align-items:baseline;padding-bottom:1rem;border-bottom:1px solid var(--line)}
.cs-amount{font-family:var(--font-display);font-size:1.6rem;color:var(--ink)}
.cs-note{font-size:.82rem;color:var(--muted);margin:1rem 0 1.4rem;line-height:1.55}
.policy-gate{display:grid;grid-template-columns:auto 1fr;gap:.7rem;align-items:start;font-size:.84rem;color:var(--muted);line-height:1.55;cursor:pointer}
.policy-gate input{width:1.05rem;height:1.05rem;accent-color:var(--amber);cursor:pointer;margin-top:.15rem}
.policy-gate a{color:var(--gold)}
.cs-checkout{width:100%;margin-top:1.3rem;border:0;cursor:pointer;font:inherit}
/* The gate made visible: a disabled button must LOOK unavailable, or people
   click it repeatedly and conclude the site is broken. */
.cs-checkout:disabled{opacity:.4;cursor:not-allowed}
.cs-continue{display:block;margin-top:1rem;text-align:center;font-size:.82rem;color:var(--muted)}
.field-error{color:#e0533f;font-size:.82rem;margin:.6rem 0 0}

@media(max-width:900px){
  .cart-grid{grid-template-columns:1fr;gap:2rem}
  .cart-summary{position:static}
}
@media(max-width:560px){
  .cart-line{grid-template-columns:4.5rem 1fr;gap:1rem}
  .cl-thumb img{width:4.5rem;height:3.4rem}
  .cl-price{grid-column:2;text-align:left;margin-top:.5rem}
}

.empty-note{padding:4rem 2rem;text-align:center;color:var(--muted);border:1px dashed var(--line);font-family:var(--font-stencil);letter-spacing:.15em;text-transform:uppercase;font-size:.85rem}

/* Footer */
/* Footer — inverted: gold background, dark text */
footer{padding:4rem 2rem 2rem;background:var(--gold);border-top:1px solid rgba(0,0,0,.22);color:#17110A}
footer .ft-logo p{color:rgba(23,17,10,.75) !important}
.ft-grid{max-width:var(--maxw);margin:0 auto;display:grid;grid-template-columns:1.4fr 1fr 1fr 1fr;gap:3rem;padding-bottom:3rem;border-bottom:1px solid rgba(0,0,0,.2)}
/* h2, not h4. These are top-level sections of the footer landmark, and on any
   page whose content stops at h1 or h2 an <h4> here was a skipped heading level
   — a screen-reader user navigating by heading hears a gap and cannot tell
   whether they missed something. Styling is unchanged; only the level moved. */
.ft-grid h2{font-family:var(--font-stencil);font-weight:700;font-size:.82rem;letter-spacing:.25em;color:#17110A;text-transform:uppercase;margin-bottom:1.1rem}
.ft-grid ul{list-style:none;padding:0;margin:0}
.ft-grid li{padding:.4rem 0;font-size:.95rem;color:rgba(23,17,10,.82)}
/* 2.5.8: these were 18px-tall targets. The link becomes the full-height box
   rather than just the glyph run — same look, tappable size. */
.ft-grid li a{display:inline-flex;align-items:center;min-height:24px;color:rgba(23,17,10,.82);transition:color .15s}
.ft-grid li a:hover{color:var(--red-text)}
/* <address> renders italic by default in every browser — reset it, this is a
   postal address and not an aside. */
.ft-address{font-style:normal;font-size:.95rem;line-height:1.55;color:rgba(23,17,10,.82);margin:0 0 .5rem}
/* Directions button. Dark plate on the gold footer with the same notched
   corner as .btn-pink, so it reads as the same family of control. Red on
   hover — the footer's only other accent. */
.btn-map{display:inline-flex;align-items:center;gap:.45rem;margin-top:.9rem;font-family:var(--font-body);font-weight:800;font-size:.72rem;letter-spacing:.16em;text-transform:uppercase;background:#17110A;color:var(--gold);padding:.62rem 1rem;
  clip-path:polygon(0 0,calc(100% - 8px) 0,100% 8px,100% 100%,8px 100%,0 calc(100% - 8px));
  transition:background .15s,color .15s,transform .15s}
.btn-map:hover{background:var(--red);color:#fff;transform:translateY(-1px)}
.btn-map:focus-visible{outline:2px solid #17110A;outline-offset:3px}
.ft-bottom{max-width:var(--maxw);margin:0 auto;padding-top:1.5rem;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:1rem}
.ft-bottom p{font-family:var(--font-stencil);font-size:.78rem;letter-spacing:.18em;color:rgba(23,17,10,.72);text-transform:uppercase;margin:0}
.ft-bottom a{display:inline-flex;align-items:center;min-height:24px;font-family:var(--font-stencil);font-size:.78rem;letter-spacing:.18em;color:#17110A;text-transform:uppercase}
.ft-bottom a:hover{color:var(--red-text)}

/* Footer legal row */
.ft-legal{max-width:var(--maxw);margin:0 auto;padding-top:1.5rem;display:flex;flex-wrap:wrap;gap:1.2rem}
.ft-legal a{display:inline-flex;align-items:center;min-height:24px;font-family:var(--font-stencil);font-size:.72rem;letter-spacing:.16em;text-transform:uppercase;color:rgba(23,17,10,.72)}
.ft-legal a:hover{color:var(--red-text)}

/* Cookie consent bar (server-side) */
.cookie-bar{position:fixed;left:1rem;right:1rem;bottom:1rem;z-index:9998;max-width:var(--maxw);margin:0 auto;
  display:flex;align-items:center;justify-content:space-between;gap:1.5rem;flex-wrap:wrap;
  background:var(--panel-2);border:1px solid var(--line);border-left:3px solid var(--gold);
  padding:1rem 1.3rem;box-shadow:0 10px 40px rgba(0,0,0,.5)}
.cookie-bar p{margin:0;color:var(--muted);font-size:.9rem}
.cookie-bar a{color:var(--gold);text-decoration:underline}
.cookie-bar form{margin:0}

/* Focus visibility */
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible,
textarea:focus-visible, summary:focus-visible, [tabindex]:focus-visible{outline:2px solid var(--gold);outline-offset:3px}

/* Responsive */
@media (max-width: 1100px){
  /* 6 nav items + logo + CTA get tight before the 720px hamburger kicks in */
  .nav-inner{gap:1.2rem;padding:1.1rem 1.5rem}
  .nav-menu{gap:1.15rem}
  .nav-menu a{font-size:.76rem;letter-spacing:.12em}
  /* Never let the CTA break across lines — it blows the nav up to two rows long
     before it actually runs out of room. (The wordmark is an image now, so it
     shrinks by height instead of wrapping.) */
  .nav .btn-pink{white-space:nowrap}
  .brand-logo{height:48px}
  footer .brand-logo{height:62px}
  .builds-grid{grid-template-columns:repeat(2,1fr)}
  .services-grid{grid-template-columns:repeat(2,1fr)}
  .brands-grid{grid-template-columns:repeat(2,1fr)}
  .quotes-grid{grid-template-columns:1fr}
  .process-grid{grid-template-columns:repeat(2,1fr)}
  .ft-grid{grid-template-columns:1fr 1fr}
  .story-grid{grid-template-columns:1fr;gap:3rem}
  .cta-inner{grid-template-columns:1fr;gap:3rem}
  /* No .stats-grid rule here any more. It used to set grid-template-columns, which
     is inert now the row is flex, and .stat's gutter is a clamp() that already
     narrows with the viewport — an override here would only fight it. */
  .shop-grid{grid-template-columns:repeat(2,1fr)}
  .pd-grid{grid-template-columns:1fr;gap:2.5rem}
}
/* Last stop before the hamburger. At 768 the row (logo 138 + menu 375 + CTA)
   exactly fills the width, so both the CTA and the wordmark wrapped to two
   lines. Shrink the button's padding and tracking, and tighten the menu with
   it — the button alone can't recover enough width. */
@media (max-width: 900px){
  .nav-inner{gap:.9rem;padding:1rem 1.25rem}
  .brand-logo{height:43px}
  .nav-menu{gap:.9rem}
  .nav-menu a{font-size:.7rem;letter-spacing:.07em}
  .nav .btn-pink{padding:.6rem .85rem;font-size:.66rem;letter-spacing:.1em;gap:.35rem}
}
@media (max-width: 720px){
  /* Phone sizes are deliberately NOT on the desktop mark's scale — the nav here is
     width-constrained by the hamburger + CTA, not height-constrained, so growing the
     mark buys nothing and costs the CTA its room. Held at the pre-2026-08-08 sizes. */
  .brand-logo{height:40px}
  footer .brand-logo{height:46px}
  .nav-menu{display:none}
  .nav-menu.open{display:flex;position:absolute;top:100%;left:0;right:0;background:var(--gold);flex-direction:column;padding:1.5rem 2rem;border-bottom:1px solid rgba(0,0,0,.22);gap:1.2rem}
  .nav-toggle{display:block}
  .hero{padding:4rem 1.5rem 3rem;min-height:80vh}
  .container-x{padding:0 1.5rem}
  footer{padding:3rem 1.5rem 1.5rem}
  .cta{padding:6rem 1.5rem 3rem}
  .shop-grid{grid-template-columns:1fr}
  /* These three kept the 1100px two-column rule all the way down, leaving ~144px
     columns. Long unbreakable words at .18em tracking ("FREIGHTLINER") then blew
     past the container and were the source of the sideways scroll. */
  .services-grid{grid-template-columns:1fr}
  .brands-grid{grid-template-columns:1fr}
  .process-grid{grid-template-columns:1fr}
  /* Footer holds two columns down to ~360px, where a nav label no longer fits
     the ~128px track and gets clipped. */
  .ft-grid{gap:2.2rem}
  /* The stamp is hung outside its box by design; on a phone that overhang is
     what pokes past the viewport edge. */
  .story-img .stamp{right:0;bottom:-1rem}
}

@media (max-width: 420px){
  /* Below ~34px the outlined "Custom Rigs" script fills in and goes muddy, so this
     is the floor — the CTA gives up padding before the mark gives up height. */
  .brand-logo{height:34px}
  /* The stats row wraps around here. A stat below its neighbour is no longer
     beside it, so the divider would hang off the left of its own line. */
  .stat{padding:0}
  .stat + .stat{border-left:0}
  .ft-grid{grid-template-columns:1fr;gap:1.8rem}
  /* .25em tracking pushes the longest tag past a 320px card. */
  .build-card .tag{letter-spacing:.16em}
}

/* Grid/flex children default to min-width:auto, so an over-wide child stretches
   the track instead of shrinking. Belt-and-braces against future overflow. */
.builds-grid>*,.services-grid>*,.brands-grid>*,.process-grid>*,
.shop-grid>*,.quotes-grid>*,.stats-grid>*,.ft-grid>*{min-width:0}

/* --- Basket: fulfilment choice + shipping breakdown -------------------------
   Shipping became a real charge on 2026-08-12 (UPS flat rates), so the basket
   now asks how the customer wants it BEFORE they reach Stripe. Radios are
   full-width hit areas: WCAG 2.5.8 wants 24x24 minimum and a label people can
   hit with a thumb. */
/* margin-top clears the Total row above: a fieldset legend sits ON the border,
   so with no gap it collided with the total and read as part of it. */
.fulfil{border:1px solid var(--line);border-radius:10px;padding:.9rem 1rem;margin:1.4rem 0 1rem;background:rgba(255,255,255,.02)}
.fulfil legend{font-family:var(--font-stencil);font-size:.7rem;letter-spacing:.16em;text-transform:uppercase;color:var(--muted);padding:0 .35rem}
.fulfil-opt{display:flex;gap:.7rem;align-items:flex-start;padding:.55rem .25rem;cursor:pointer;min-height:44px}
.fulfil-opt+.fulfil-opt{border-top:1px solid var(--line)}
.fulfil-opt input{margin-top:.25rem;width:18px;height:18px;flex:0 0 auto;accent-color:var(--red)}
.fulfil-opt strong{display:block;color:var(--ink);font-size:.95rem}
.fulfil-opt em{display:block;font-style:normal;color:var(--muted);font-size:.85rem;margin-top:.1rem}
.cs-row-sub{font-size:.85rem;color:var(--muted);margin-top:-.35rem}
.cs-row-total{border-top:1px solid var(--line);margin-top:.6rem;padding-top:.6rem;font-weight:700}
.cs-row-total .cs-amount{color:var(--gold);font-size:1.15rem}
/* Composition note under a summary row (e.g. "$500 + $400 extras handling").
   Deliberately has NO amount in the money column: it explains how the figure
   beside it was reached, and an amount here read as a separate charge. */
.cs-breakdown{display:block;font-size:.75rem;color:var(--muted);margin-top:.15rem;letter-spacing:.02em}
/* The amount column uses the big display face because it holds money. When it
   holds a short phrase instead ("Calculated at checkout") that face is far too
   loud and wraps badly — this drops it back to body type without moving it out
   of the column. */
.cs-amount-note{font-family:var(--font-body);font-size:.82rem;font-weight:500;color:var(--muted);text-align:right;line-height:1.35;max-width:9rem}

/* Consent gate on the contact and quote forms. Reuses .policy-gate's layout
   (the basket's) so the three gates on the site look and behave alike; this
   only adds the spacing the standalone forms need.

   No max-width. It carried 52ch, which at .84rem is ~364px — narrower than the
   form itself, so a 66-character sentence broke onto a second line with one
   word stranded on it. The sentence is short enough to want the form's full
   width and to wrap on its own when the screen is genuinely narrow. */
.consent-gate{margin:1.5rem 0 .25rem}

/* Screen-reader-only. The site had no such utility, so a <caption> added for
   assistive tech rendered as visible body copy in the middle of the invoice.
   Clip rather than display:none — display:none removes it from the
   accessibility tree too, which defeats the point of having written it. */
.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0;
}

/* --- Invoice / proforma page (public, token-reached) ---------------------
   Client, 2026-08-13: proformas from quotes and invoices for phone orders,
   payable by card. This is the page the emailed link lands on.

   Uses the site's own tokens rather than new colours: a customer arriving
   here from an email needs to recognise it as Arroyo before they type a card
   number into the next screen. */
.invoice-sheet{max-width:52rem;margin-inline:auto}

.invoice-alert{
  margin:0 0 1.5rem;padding:.85rem 1rem;
  border-left:4px solid var(--red);background:rgb(206 32 41 / .08);
}
.invoice-paid{
  margin:0 0 1.5rem;padding:.85rem 1rem;
  border-left:4px solid var(--gold);background:rgb(242 184 7 / .1);
}

.invoice-meta{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(13rem,1fr));
  gap:1.5rem;margin:0 0 2rem;
}
.invoice-meta p{margin:.25rem 0 0;line-height:1.55}
.invoice-meta__label{
  font-size:.72rem;letter-spacing:.1em;text-transform:uppercase;opacity:.65;
}

.invoice-lines{width:100%;border-collapse:collapse;margin:0 0 1.75rem}
.invoice-lines th,.invoice-lines td{padding:.85rem 0;text-align:left;vertical-align:top}
.invoice-lines thead th{
  font-size:.72rem;letter-spacing:.09em;text-transform:uppercase;opacity:.65;
  border-bottom:2px solid currentColor;padding-bottom:.5rem;
}
.invoice-lines tbody td{border-bottom:1px solid rgb(255 255 255 / .12)}
.invoice-lines .num{text-align:right;white-space:nowrap;font-variant-numeric:tabular-nums}
/* Options are part OF the item above, not separate purchases — the rule and
   indent say so without needing a word of explanation. */
.invoice-option{
  display:block;margin-top:.3rem;padding-left:.8rem;
  border-left:2px solid var(--gold);opacity:.8;font-size:.94em;
}
/* The shop's free-text description of a custom job. A neutral rule rather than
   the gold one used for priced options: this is explanation, not another
   charge, and the two must not read as the same kind of line.
   pre-line keeps the line breaks whoever typed it intended. */
.invoice-details{
  display:block;margin-top:.4rem;padding-left:.8rem;
  border-left:2px solid rgba(255,255,255,.18);
  opacity:.75;font-size:.94em;line-height:1.5;white-space:pre-line;
}
.invoice-lines tfoot th{text-align:right;font-weight:400;opacity:.8}
.invoice-lines tfoot .invoice-total th,
.invoice-lines tfoot .invoice-total td{
  padding-top:1.1rem;font-size:1.15rem;font-weight:700;opacity:1;
}
.invoice-lines tfoot .invoice-total td{color:var(--gold)}

.invoice-pay{margin:0}
.invoice-pay__btn{font-size:1.05rem;padding:1rem 1.9rem}
.invoice-fineprint{margin:1rem 0 0;font-size:.92rem;line-height:1.6;opacity:.75;max-width:52ch}

/* Narrow screens: the qty column is the one that can go, since every line
   on these invoices is a single fabricated item far more often than not. */
@media (max-width:32rem){
  .invoice-lines thead th:nth-child(2),
  .invoice-lines tbody td:nth-child(2){display:none}
}
