/* ============================================================================
   ARMINAK CARAVAN — Design System v2
   Minimal / futuristic e-commerce. Light + dark themes.
   The cinematic hero keeps its own fixed palette (video never changes theme).
   ============================================================================ */

/* ---------------------------------------------------------------- 1. TOKENS */

:root {
  /* Light — one noble tone. The client rejected both the beige and the
     black-and-white grounds and asked for "глубокий светлый кремовый /
     слоновая кость": ivory, with surfaces only a shade apart so the page
     reads as a single field rather than white-plus-beige. */
  --bg:            #F7F4ED;   /* ivory ground */
  --bg-elev:       #FCFAF5;   /* lifted surfaces: drawer, form cards */
  --tile:          #EFEAE0;   /* product surfaces */
  --tile-strong:   #E7E1D4;
  --ink:           #1B2A41;   /* deep navy */
  --ink-soft:      #3C4A5F;
  --muted:         #6E7889;
  --accent:        #B08D57;   /* sand gold / amber */
  --accent-hover:  #8F7245;
  --accent-soft:   rgba(176, 141, 87, 0.14);
  --sand:          #C7A87A;   /* the grains at the edges */
  --hairline:      rgba(27, 42, 65, 0.13);
  --hairline-soft: rgba(27, 42, 65, 0.07);
  --hairline-strong: rgba(27, 42, 65, 0.30);   /* dashed borders — 0.13 vanishes */
  /* Kept as definitions only — nothing consumes these any more. They existed
     so a band could flip against the theme, which is precisely what stopped
     the pages reading as one document, so reaching for them again is almost
     certainly the wrong move. */
  --inverse-bg:    #1B2A41;
  --inverse-ink:   #F7F4ED;
  --inverse-muted: rgba(247, 244, 237, 0.62);
  --scrim-nav:     rgba(247, 244, 237, 0.86);
  --badge-sale:    #B08D57;
  --focus:         #1B2A41;

  /* Type */
  --font-display: "Cormorant Garamond", "Times New Roman", serif; /* press marks only */
  --font-ui: "Manrope", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Rhythm */
  --nav-h: 76px;
  --gutter: clamp(22px, 3.4vw, 56px);

  /* The content measure. .shell and .nav__inner are centred boxes of this
     width with --gutter of padding, so the content edge sits at
     (container - measure)/2 + gutter — which equals --gutter only while the
     window is narrower than the measure. Anything positioned from the
     container edge instead has to add that centring back or it drifts left on
     every display wider than this. */
  --measure: 1560px;
  --radius: 10px;      /* tiles */
  --radius-s: 6px;     /* controls */
  --radius-pill: 999px;

  /* ---- Display scale -------------------------------------------------------
     The site is drawn at 90%. Everything — type, spacing, hairlines, the film,
     the seal — comes down by a tenth, which is the density the client works at
     and asked to keep.

     `zoom` on the root rather than a rewrite of every clamp(): there are about
     ninety fluid sizes in this file and no single number they all pass through,
     so scaling them by hand would be ninety chances to get one wrong and no way
     to tell. zoom scales used values after layout resolves, so proportions,
     line breaks and the fluid clamps all survive exactly as authored.

     Two things it does NOT scale, and both are handled where they occur:
       - Viewport units. `100vh` still resolves against the real viewport and is
         then scaled down, so anything meant to FILL the screen has to be
         divided by --zoom to get back to full. Grep --zoom to find all four.
       - Media queries, which keep evaluating against the true viewport. So the
         breakpoints fire at the same window sizes as before rather than at the
         same content sizes. The layout is fluid between them, so this shows up
         as slightly roomier content before each breakpoint — not as a break. */
  --zoom: 0.9;
  zoom: var(--zoom);

  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --dur: 600ms;

  /* Numbered manifest. Three sections list things — the corridor waypoints,
     the client qualification, the footer columns — and each had invented its
     own numeral size, gutter and row height, which is most of why the lower
     page read as assembled rather than designed. One spec, three call sites. */
  --idx-size:   11px;   /* mono numeral */
  --idx-gutter: 46px;   /* fixed column the numeral sits in */
  --idx-row-y:  15px;   /* vertical padding either side of a row */
}

/* Dark — the premium matte alternative the client offered as option two.
   Deep, low-sheen, faintly warm; never pure black. */
[data-theme="dark"] {
  --bg:            #15161B;
  --bg-elev:       #1B1D23;
  --tile:          #1F2129;
  --tile-strong:   #272A33;
  --ink:           #F2EDE3;
  --ink-soft:      #CFC9BE;
  --muted:         #8A92A1;
  --accent:        #C9A570;
  --accent-hover:  #D8B885;
  --accent-soft:   rgba(201, 165, 112, 0.16);
  --sand:          #C9A570;
  --hairline:      rgba(242, 237, 227, 0.14);
  --hairline-soft: rgba(242, 237, 227, 0.07);
  --hairline-strong: rgba(242, 237, 227, 0.32);
  --inverse-bg:    #F7F4ED;
  --inverse-ink:   #1B2A41;
  --inverse-muted: rgba(27, 42, 65, 0.62);
  --scrim-nav:     rgba(21, 22, 27, 0.84);
  --badge-sale:    #C9A570;
  --focus:         #F2EDE3;
}

/* ------------------------------------------------------------ 2. RESET/BASE */

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

html {
  -webkit-text-size-adjust: 100%;
  overflow-x: hidden;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--nav-h) + 24px);
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 15.5px;
  font-weight: 400;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-feature-settings: "kern" 1, "liga" 1;
  overflow-x: hidden;
  transition: background 400ms var(--ease), color 400ms var(--ease);
}

body.no-scroll { overflow: hidden; }

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

h1, h2, h3, h4 { margin: 0; font-weight: 600; line-height: 1.12; letter-spacing: -0.015em; }
p { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
a { color: inherit; text-decoration: none; }
button, input, select, textarea { font: inherit; color: inherit; }

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

:focus-visible { outline: 1.5px solid var(--focus); outline-offset: 3px; border-radius: 2px; }

/* ------------------------------------------ 2b. (SAND EDGES, REMOVED) ----- */
/* The screen-margin grains lived here: a fixed, masked tile with a rendered
   alpha clip and a WebGL veil layered over it as upgrades. The client
   originally marked them mandatory and has now asked for them gone — "remove
   the elements on the sides of the site and keep just the video itself".

   Deliberately deleted rather than hidden. Three stacked fallbacks and a
   codec-probing loader left behind a display:none rule would be dead weight
   nobody could safely touch later. The shader itself (sand.js / ACSand) stays,
   because the hero still draws its sand with it — see mountHeroSand.

   git log is the way back: this block and its JS were removed together in one
   commit. */

/* The grains float over the page — that is the "camels inside the site"
   feeling the client asked for. Masked to the margins and pointer-transparent,
   so they never touch legibility or interaction. Nav and drawer stay above. */

/* ------------------------------------------------------------ 3. TYPOGRAPHY */

.t-section {
  font-size: clamp(28px, 3vw, 42px);
  font-weight: 600;
  letter-spacing: -0.025em;
  line-height: 1.08;
}

.t-sub {
  font-size: clamp(22px, 2.2vw, 30px);
  font-weight: 600;
  letter-spacing: -0.02em;
}

.t-card { font-size: 16.5px; font-weight: 600; letter-spacing: -0.01em; }

.label {
  font-size: 11px;
  font-weight: 600;
  line-height: 1.4;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--accent);
}


.body-copy { font-size: 15px; line-height: 1.7; color: var(--muted); }
.body-copy--ink { color: var(--ink-soft); }
.lede { font-size: 16.5px; line-height: 1.7; color: var(--muted); }

.tabular { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }

/* ------------------------------------------------------------- 4. STRUCTURE */

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

.section { padding: clamp(72px, 8vw, 128px) 0; }
.section--tight { padding: clamp(48px, 5vw, 80px) 0; }

/* Was a hard --inverse-bg slab, which flipped against the theme exactly like
   the footer did. A closing band still needs to read as an ending, so it takes
   the one-shade-up surface and a hairline at each edge instead of a colour
   reversal — punctuation, not a different room. */
.section--inverse {
  background: var(--tile);
  color: var(--ink);
  border-top: 1px solid var(--hairline-soft);
  border-bottom: 1px solid var(--hairline-soft);
}
.section--inverse h1, .section--inverse h2, .section--inverse h3 { color: var(--ink); }

/* Legacy aliases from v1 markup */
.section--navy { background: var(--tile); color: var(--ink); }
.section--navy h1, .section--navy h2, .section--navy h3 { color: var(--ink); }
.section--sand2 { background: var(--tile); }

.hairline { height: 1px; border: 0; margin: 0; background: var(--hairline); }

/* Section head row: title cluster left, action right */
.rowhead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 28px;
  flex-wrap: wrap;
  margin-bottom: clamp(28px, 3vw, 44px);
}

/* Dark mode reverses the seal — currentColor makes the disc warm off-white —
   so the sand has to come down or it vanishes into its own disc. */
[data-theme="dark"] .lockup { --mark-sand: #8C6E3F; --mark-lit: #C9A570; }

/* ------------------------------------------------------------- 4b. LOCKUP */
/* The company signature. ONE component, so the bar, the navigation panel and
   the footer sign the same name in the same face at three sizes — before this
   the name was set five different ways and the site never read as one brand.

   Direction C, "Seal" (brand/README.md). Every dimension derives from
   --lockup, and the ratios are build_logo.py's own: the wordmark is
   NAME_SZ/MARK = 62/128 of the mark, the gap is GAP/MARK = 30/128. Change
   --lockup and the whole signature scales correctly; there is no second
   number to keep in sync.

   The mark is inline SVG rather than an <img> because the disc takes
   currentColor. That is the only reason one file can be navy on the page,
   cream over the film and warm off-white in dark mode — an image would need
   three files and a rule to pick between them. */

.lockup {
  --lockup: 34px;
  --mark-sand: var(--accent);
  /* The tone the seal warms to on hover and on the way into view. Every
     context overrides --mark-sand for its own ground and --mark-lit with it, so
     "lit" is one rule here and never a per-context special case. */
  --mark-lit: #D9BA83;
  display: inline-flex;
  align-items: center;
  gap: calc(var(--lockup) * 0.234);
  min-width: 0;
  color: var(--ink);
  text-decoration: none;
  transition: color 450ms var(--ease);
}

.lockup__mark {
  width: var(--lockup);
  height: var(--lockup);
  flex: none;
  display: block;
  border-radius: 50%;
  /* The ring is the whole trick. The mark used to be inline SVG whose disc took
     currentColor, which is how one file read navy on the page, cream over the
     film and warm off-white in dark mode. A photograph cannot recolour — so the
     adaptation moves out of the picture and onto a ring drawn around it, and
     the picture itself is the same bytes everywhere.

     box-shadow, not border: a border would eat into the element's box and
     shrink the photograph by twice the ring width at every size. A spread-only
     shadow sits entirely outside it, so --lockup stays the diameter of the
     image and the ring is free. */
  box-shadow: 0 0 0 var(--mark-ring-w, 1.5px) var(--mark-ring, var(--ink));
  transition: width 350ms var(--ease), height 350ms var(--ease),
              box-shadow 450ms var(--ease), filter 700ms var(--ease);
}

/* The disc is a night photograph, so on a dark ground its own sky merges with
   the page and the circle loses its edge. Cream ring there, navy on the page. */
[data-theme="dark"] .lockup { --mark-ring: var(--ink); }
.nav--over .nav__mark { --mark-ring: #FDF6EA; }

/* ---- lit ------------------------------------------------------------------
   The old hover raised the sun and bloomed it — both SVG elements that no
   longer exist. A photograph gets a photographic gesture instead: it lifts a
   little and the moonlight comes up. Hover and focus hold it; scrolling to the
   footer mark plays it once (main.js sets .in-view). */
.lockup:hover .lockup__mark,
.lockup:focus-visible .lockup__mark {
  filter: brightness(1.14) saturate(1.06);
  box-shadow: 0 0 0 var(--mark-ring-w, 1.5px) var(--mark-ring, var(--ink)),
              0 0 calc(var(--lockup) * 0.16) rgb(from var(--accent) r g b / 0.45);
}

@media (prefers-reduced-motion: no-preference) {
  .lockup.in-view .lockup__mark { animation: markDawn 1500ms var(--ease) both; }
}

@keyframes markDawn {
  0%   { filter: brightness(0.72) saturate(0.85); }
  55%  { filter: brightness(1.16) saturate(1.08); }
  100% { filter: brightness(1)    saturate(1); }
}

/* The text column. One structure for both lockups: the reduced one holds the
   wordmark alone, the full one adds the descriptor under it. */
.lockup__text {
  display: flex;
  flex-direction: column;
  /* dir_c() puts the wordmark's baseline at 0.545 of the mark and the
     descriptor's at 0.837 — 0.292 apart. Both lines are set solid, so this gap
     makes up the difference the two line boxes do not already cover; measured
     against brand/png/arminak-caravan-c-horizontal-colour.png rather than
     derived, because the shortfall depends on Cormorant's own metrics. */
  gap: calc(var(--lockup) * 0.142);
  min-width: 0;
}

.lockup__name {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: calc(var(--lockup) * 0.484);
  letter-spacing: 0.115em;
  line-height: 1;
  text-transform: uppercase;
  white-space: nowrap;
  /* Optical, not arithmetic. align-items centres the line box, and a line box
     reserves descender depth that all-caps never uses — so centred caps sit
     visibly high against the disc. This drops them back onto the mark's axis.
     The trailing letter-space also pushes the word left of true centre, which
     the negative right margin takes back. */
  margin: 0.055em -0.115em 0 0;
  transition: font-size 350ms var(--ease);
}

/* The third line of the full lockup — Manrope 600 at 0.30em and 12/128 of the
   mark, which is dir_c()'s setting exactly. It is why the footer lockup is as
   large as it is: below about a 100px mark this line is under 9px and stops
   being type. The bar therefore carries the reduced lockup, not this one. */
.lockup__desc {
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: calc(var(--lockup) * 0.09375);
  letter-spacing: 0.30em;
  line-height: 1;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--mark-sand);
  margin-right: -0.30em;
  transition: color 450ms var(--ease), font-size 350ms var(--ease);
}

/* ------------------------------------------------------------ 5. NAVIGATION */

.nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  height: var(--nav-h);
  display: flex;
  /* stretch, not center. Centering sized .nav__inner by its own content and
     then centred it, so anything inside taller than the bar hung out below it
     — which is what put the mark and the quotation button on top of the page,
     with section headings reading straight through them. Stretching pins the
     inner to exactly the bar's height and lets its own grid centre the items
     inside that, so nothing can render outside the bar by construction. */
  align-items: stretch;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: border-color 450ms var(--ease);
}

/* The frosted plate, on a pseudo-element rather than on .nav itself.
   backdrop-filter makes its element a containing block for position:fixed
   descendants, and .nav__menu is exactly that on narrow screens — put the
   filter on .nav and the mobile panel stops being measured against the
   viewport and collapses into the bar. A pseudo-element has no descendants to
   trap, so the effect is free of that. */
.nav::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  /* 0.78, not the 0.55 this started at. Rendered without the blur — which is
     what a snapshot or an engine that skips backdrop-filter gives you — 0.55
     ivory left a section heading perfectly legible straight through the bar.
     The tint has to carry the occlusion on its own and the blur has to be the
     thing that makes it feel like glass, never the thing doing the work. */
  background: rgb(from var(--bg) r g b / 0.78);
  -webkit-backdrop-filter: blur(24px) saturate(1.35);
          backdrop-filter: blur(24px) saturate(1.35);
  opacity: 0;
  transition: opacity 450ms var(--ease);
}
.nav--scrolled::before { opacity: 1; }

/* No blur available: a 0.55 tint alone would let text read through, so the
   plate goes solid instead. Same for engines without relative colour. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .nav::before { background: var(--bg); }
}
@supports not (color: rgb(from white r g b / 1)) {
  .nav::before { background: var(--scrim-nav); }
}

/* Three tracks rather than an absolutely-centred mark: the wordmark keeps the
   reference-style centre position, but the side columns can no longer run
   underneath it — which they did in Russian, where the link labels are far
   longer than the English. */
.nav__inner {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 0;
  max-width: var(--measure);
  margin: 0 auto;
  padding: 0 var(--gutter);
  display: grid;
  /* TWO tracks, not three. The bar is one cluster at each end — the way in and
     who this is on the left, the language and the ask on the right — so it
     wants exactly two columns. The old third track existed because the trigger
     and the mark were separate grid items, and since BOTH declared
     `grid-column: 1` the grid stacked them into two rows: the trigger in the
     bar, the mark outside it and over the page. They are one .nav__lead now,
     so there is nothing left to collide. */
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: clamp(24px, 4vw, 64px);
}

/* The left cluster: the way in, then who this is. One grid item, so the two
   can never land on different rows again — which is exactly what happened when
   they were two items both claiming column 1. Wrapping them also means the
   logo keeps its distance from the trigger at every width for free, with no
   breakpoint doing the arranging. */
.nav__lead {
  grid-column: 1;
  display: flex;
  align-items: center;
  /* Reads narrow because .navtrig carries 11px of its own padding on this
     side — the optical gap is this plus that. */
  gap: clamp(6px, 0.8vw, 13px);
  min-width: 0;
}

/* The mark is now the shared .lockup — the roundel and the wordmark together,
   in the brand's own face. It carried a typed Manrope wordmark before, which
   is why the bar and the footer signed two different names. */
.nav__mark { --lockup: 34px; }

/* The page links that used to sit here are gone — they live in the panel now.
   .nav__menu, .nav__link and .nav__burger went with them, base rules and
   mobile-panel rules both: the bar no longer has a second layout below 1024,
   because the trigger is the same control at every width. */

.nav__side {
  grid-column: 2;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: clamp(12px, 1.4vw, 20px);
}

/* The desk CTA. The site's whole conversion is "request a quotation" and the
   bar carried no way to do it. */
.nav__cta {
  height: 38px;
  padding: 0 18px;
  font-size: 13px;
  background: var(--ink);
  border-color: var(--ink);
  color: var(--bg);
}
.nav__cta:hover { background: var(--accent); border-color: var(--accent); color: #1B2A41; }

/* Reading position, drawn as the corridor route completing along the bar's
   bottom edge. Scroll-timeline only — no scroll handler, and browsers without
   it simply never paint it. */
.nav__progress {
  position: absolute;
  left: 0; bottom: -1px;
  height: 1.5px;
  width: 100%;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  pointer-events: none;
}
@supports (animation-timeline: scroll()) {
  .nav__progress {
    animation: navProgress linear both;
    animation-timeline: scroll(root block);
  }
}
@keyframes navProgress { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* Panel-only tail: hidden while the menu is a horizontal bar. */

/* Theme toggle */
.theme-toggle {
  appearance: none;
  background: none;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-pill);
  width: 56px; height: 30px;
  padding: 0 4px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  position: relative;
  transition: border-color 400ms var(--ease), background 400ms var(--ease);
}

.theme-toggle:hover { border-color: var(--muted); }

.theme-toggle__thumb {
  width: 22px; height: 22px;
  border-radius: 50%;
  background: var(--ink);
  color: var(--bg);
  display: grid;
  place-items: center;
  transform: translateX(0);
  transition: transform 450ms var(--ease), background 400ms var(--ease), color 400ms var(--ease);
}

[data-theme="dark"] .theme-toggle__thumb { transform: translateX(26px); }

.theme-toggle .i-sun, .theme-toggle .i-moon { position: absolute; width: 11px; height: 11px; }
.theme-toggle .i-sun { display: block; position: static; }
.theme-toggle .i-moon { display: none; position: static; }
[data-theme="dark"] .theme-toggle .i-sun { display: none; }
[data-theme="dark"] .theme-toggle .i-moon { display: block; }

/* ---- Segmented control ---------------------------------------------------
   One control, two settings: language and appearance, in the navigation panel.
   They are built from the same parts so the panel reads as one preferences
   pair rather than a text toggle beside a switch.

   inline-grid with equal auto-columns, not flex: it makes both halves exactly
   the same width whatever their labels say, which is the only reason a thumb
   at 50% can be right. "EN"/"RU" happen to match; "Light"/"Dark" do not, and
   in Russian neither pair does.

   The thumb is absolutely positioned, which also takes it out of the grid flow
   so it never becomes a third column.                                       */
.seg {
  position: relative;
  display: inline-grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 2px;
  padding: 3px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-pill);
  transition: border-color 400ms var(--ease);
}
.seg:hover { border-color: var(--muted); }

.seg__btn {
  position: relative;
  z-index: 1;
  appearance: none;
  background: none;
  border: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 6px 13px;
  border-radius: var(--radius-pill);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--muted);
  transition: color 320ms var(--ease);
}
.seg__btn svg { width: 12px; height: 12px; flex: none; }
.seg__btn:hover { color: var(--ink); }
.seg__btn[aria-pressed="true"],
.seg__btn[aria-pressed="true"]:hover { color: var(--bg); }

.seg__thumb {
  position: absolute;
  top: 3px;
  bottom: 3px;
  left: 3px;
  width: calc((100% - 6px - 2px) / 2);
  border-radius: var(--radius-pill);
  background: var(--ink);
  pointer-events: none;
  transition: transform 380ms var(--ease), background 400ms var(--ease);
}
/* :last-of-type counts buttons only — the thumb is a span — so this is "the
   right-hand half is the one that is on". */
.seg:has(.seg__btn:last-of-type[aria-pressed="true"]) .seg__thumb {
  transform: translateX(calc(100% + 2px));
}

/* No :has(): the pressed half keeps a filled ground of its own, so the control
   still reads correctly, it simply does not slide. */
@supports not selector(:has(*)) {
  .seg__thumb { display: none; }
  .seg__btn[aria-pressed="true"] { background: var(--ink); }
}

/* ---- the panel's preferences strip ---- */
/* margin-top:auto moves here from .navpanel__foot so the two sit together as
   one block at the bottom of the sheet. Two auto margins in a flex column
   would split the free space between them and separate the pair. */
.navpanel__prefs {
  margin-top: auto;
  display: grid;
  gap: 12px;
  padding-top: clamp(18px, 2.4vh, 26px);
  border-top: 1px solid var(--hairline-soft);
}
.navpanel__pref {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}
.navpanel__prefLabel {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Language toggle — text pair, minimal. Still used in the footer, where it is
   a plain pair rather than a segmented control. */
.lang { display: inline-flex; align-items: center; gap: 2px; }

.lang__btn:not(.seg__btn) {
  appearance: none;
  background: none;
  border: 0;
  cursor: pointer;
  padding: 5px 7px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--ink);
  opacity: 0.38;
  border-radius: var(--radius-s);
  transition: opacity 300ms var(--ease), color 450ms var(--ease);
}

.lang__btn:not(.seg__btn):hover { opacity: 0.75; }
.lang__btn:not(.seg__btn)[aria-pressed="true"] { opacity: 1; text-decoration: underline; text-underline-offset: 4px; }

/* Over the film — the hero is a dark plate again, so the nav borrows the
   hero's cream until the page scrolls past it. Only pages that actually have
   a #hero ever get this class (main.js). */
.nav--over .nav__mark,
.nav--over .navtrig,
.nav--over .lang__btn { color: #FDF6EA; }

/* The hero film is dark where the bar sits, so cream needed no help there.
   The interior pages open on stills that are hazy sky and pale dune in exactly
   that band, and the language pair disappeared into them. A shadow holds
   whatever the frame is doing; a scrim tuned to one image does not. */
.nav--over .nav__mark,
.nav--over .navtrig,
.nav--over .lang__btn,
.nav--over .nav__cta { text-shadow: 0 1px 14px rgba(20, 14, 6, 0.55); }
/* currentColor already turns the seal's disc cream; the sand has to come down
   to a tone that still reads against it, since gold on cream barely does. */
.nav--over .nav__mark { --mark-sand: #9A7743; --mark-lit: #D8B173; }
.nav--over .navtrig__bars i { background: #FDF6EA; }
.nav--over :focus-visible { outline-color: #FDF6EA; }

/* Over the film the bar has no ground of its own, so the solid CTA becomes an
   outline and the progress rule steps back to cream. */
.nav--over .nav__cta {
  background: transparent;
  border-color: rgba(253, 246, 234, 0.45);
  color: #FDF6EA;
}
.nav--over .nav__cta:hover { background: #FDF6EA; border-color: #FDF6EA; color: #2B1D0F; }
.nav--over .nav__progress { background: rgba(253, 246, 234, 0.75); }

/* Scrolled. Over the film the bar is furniture on a photograph and reads fine
   at full height; once it is a solid strip sitting on the page it is competing
   with the content under it, and at 76px with every control still in it that
   is a lot of bar. So it condenses: shorter, smaller mark, smaller button.

   --nav-h is redefined here rather than on :root because .nav__menu is a child
   and inherits it — the mobile panel therefore keeps meeting the bar exactly,
   with no second rule to keep in sync. The root value still drives
   scroll-padding-top, which is correctly the taller of the two. */
.nav--scrolled {
  --nav-h: 58px;
  /* The surface itself is .nav::before above — a real blur plus a 0.55 tint,
     so content passing under the bar is softened out rather than read through
     it. The old --scrim-nav was rgba(247,244,237,0.86) on a #F7F4ED page: 86%
     ivory over ivory, invisible by construction, occluding nothing. */
  border-bottom-color: var(--hairline);
}
.nav--scrolled .nav__mark { --lockup: 27px; }
.nav--scrolled .nav__cta { height: 38px; padding: 0 18px; font-size: 12.5px; }

/* Three controls have now left this bar: the page links, the theme toggle and
   the language pair. All three are in the panel, which is where navigation and
   preferences both belong.

   What is left is a mark and one action, at every width and every scroll
   position — no control appears or disappears as you move down the page, which
   is what the old "language stands down on scroll" rule did and the reason the
   top right used to feel unsettled. */

/* The lockup's own transitions live on .lockup__mark and .lockup__name, which
   is what makes --lockup animate as one signature rather than as three parts
   arriving separately. */
.nav__cta {
  transition: height 350ms var(--ease), padding 350ms var(--ease),
              background 450ms var(--ease), border-color 450ms var(--ease),
              color 450ms var(--ease);
}

/* ---- Bar entrance ---------------------------------------------------------
   The bar used to be simply present on first paint, all seven things at once,
   which is why the buttons top-right announced themselves before the mark did.
   It now assembles left to right in the order a reader would build it: mark,
   sections, language, action.

   Pure CSS on purpose. A CSS animation runs once when the element is created,
   which is exactly "every load and every refresh" with no JS, no class to add
   and remove, and no chance of the bar being stuck invisible if a script
   fails — `both` fill only holds the start state through the delay, and the
   delays are short enough that a failed stylesheet is the only way to lose it.

   Colour is deliberately not animated here: .nav--over swaps the mark and
   links to cream over the film, and animating colour would fight it. This
   moves opacity and Y only.                                                 */

@media (prefers-reduced-motion: no-preference) {
  .navtrig,
  .nav__mark,
  .nav__side > * {
    animation: navIn 620ms var(--ease) both;
  }

  /* Left to right, in the order a reader builds the bar: the way in, who this
     is, the language, the ask. */
  .navtrig             { animation-delay: 220ms; }
  .nav__mark           { animation-delay: 380ms; }
  /* Was 860ms, behind a language pair that is no longer in front of it. */
  .nav__side > .nav__cta { animation-delay: 620ms; }
}

@keyframes navIn {
  from { opacity: 0; transform: translateY(-7px); }
}

/* Burger */
/* --------------------------------------------------- 5b. NAVIGATION PANEL */
/* The site's only navigation. A trigger at the top left, and one sheet holding
   both the pages and the current page's sections.

   It replaced three things — page links in the bar, a burger panel below
   1024px, and a floating section rail — so the important property is that it is
   the SAME control at every width. There is no viewport at which navigation
   works differently, which is the thing that made the old arrangement feel like
   two systems bolted together.                                              */

.navtrig {
  display: flex;
  align-items: center;
  gap: 10px;
  appearance: none;
  background: none;
  border: 0;
  padding: 6px 2px;
  margin-left: -2px;
  cursor: pointer;
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  transition: color 450ms var(--ease);
}

.navtrig__bars { display: grid; gap: 3.5px; width: 18px; }
.navtrig__bars i {
  display: block;
  height: 1.5px;
  background: currentColor;
  border-radius: 1px;
  transition: transform 400ms var(--ease), opacity 250ms var(--ease),
              background 450ms var(--ease);
}

/* Middle bar is shorter at rest, so the icon reads as an index rather than a
   hamburger — three equal lines is the most generic mark on the web. */
.navtrig__bars i:nth-child(2) { width: 70%; }

.navtrig[aria-expanded="true"] .navtrig__bars i:nth-child(1) { transform: translateY(5px) rotate(45deg); }
.navtrig[aria-expanded="true"] .navtrig__bars i:nth-child(2) { opacity: 0; }
.navtrig[aria-expanded="true"] .navtrig__bars i:nth-child(3) { transform: translateY(-5px) rotate(-45deg); }

.navtrig:hover { color: var(--accent-hover); }
.nav--over .navtrig:hover { color: var(--hero-accent, #E3C07A); }

/* No text label. "Index" was the widest thing on the left of the bar and it
   pushed the logo off the optical margin to say what three stacked lines and
   an aria-label already say. The bars are the whole control now, so they get
   a hit area instead of a word. */
.navtrig {
  min-height: 36px;
  /* Padding grows the target to 36px; the matching negative margin puts the
     bars' left edge back exactly on the gutter, because the left edge of this
     cluster is what sets the page's optical margin. Centring the bars in a
     wider box instead would shift them inboard and the whole header would sit
     off-axis from the content below it. */
  padding: 9px 11px;
  margin: 0 0 0 -11px;
}

/* ---- the sheet ---- */

.navpanel {
  position: fixed;
  inset: 0;
  z-index: 200;                       /* over the bar (100) — it covers it */
  visibility: hidden;
  transition: visibility 0s linear 460ms;
}
.navpanel[data-open="true"] { visibility: visible; transition-delay: 0s; }

.navpanel__scrim {
  position: absolute;
  inset: 0;
  background: rgba(12, 16, 24, 0.42);
  -webkit-backdrop-filter: blur(3px);
          backdrop-filter: blur(3px);
  opacity: 0;
  transition: opacity 460ms var(--ease);
}
.navpanel[data-open="true"] .navpanel__scrim { opacity: 1; }

/* Slides from the left, because that is where the trigger is — a sheet that
   arrives from the side you did not press reads as unrelated to the press. */
.navpanel__sheet {
  position: absolute;
  top: 0; left: 0; bottom: 0;
  width: min(420px, 88vw);
  display: flex;
  flex-direction: column;
  gap: clamp(22px, 3vh, 38px);
  padding: clamp(22px, 3vw, 34px) clamp(24px, 3vw, 40px) clamp(26px, 3vw, 36px);
  background: var(--bg);
  border-right: 1px solid var(--hairline);
  overflow-y: auto;
  overscroll-behavior: contain;
  transform: translateX(-100%);
  transition: transform 460ms var(--ease);
}
.navpanel[data-open="true"] .navpanel__sheet { transform: none; }

.navpanel__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
}

/* The panel used to head itself with the name in JetBrains Mono at 0.18em —
   a third setting of the same word, in a face the logo does not use. It is the
   lockup now, like everywhere else. */
.navpanel__eyebrow { --lockup: 26px; }

.navpanel__close {
  appearance: none;
  background: none;
  border: 1px solid var(--hairline);
  border-radius: 50%;
  width: 34px; height: 34px;
  display: grid;
  place-items: center;
  cursor: pointer;
  color: var(--ink);
  transition: border-color 300ms var(--ease), color 300ms var(--ease);
}
.navpanel__close svg { width: 15px; height: 15px; }
.navpanel__close:hover { border-color: var(--ink); }

/* ---- groups ---- */

/* Same numbered-manifest spec as the corridor waypoints, the qualification
   ledger and the footer columns. The navigation is not exempt from the page's
   own system. */
.navpanel__h {
  display: grid;
  grid-template-columns: var(--idx-gutter) 1fr;
  align-items: baseline;
  padding-bottom: 11px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--hairline-soft);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  color: var(--ink);
}
.navpanel__h i {
  font-family: var(--font-mono);
  font-style: normal;
  font-size: var(--idx-size);
  font-weight: 500;
  letter-spacing: 0.06em;
  color: var(--accent);
}

.navpanel__list { display: flex; flex-direction: column; }

/* The tick-and-label language the rail used, kept because it was the part of
   that experiment worth keeping — a length change reads as position where a
   colour change alone does not. */
.navpanel__link {
  display: grid;
  grid-template-columns: 26px 1fr;
  align-items: center;
  gap: 14px;
  padding: 11px 0;
  color: var(--ink-soft);
  transition: color 260ms var(--ease);
}

.navpanel__link i {
  display: block;
  height: 1px;
  width: 14px;
  background: currentColor;
  opacity: 0.4;
  transition: width 380ms var(--ease), opacity 260ms var(--ease),
              background 260ms var(--ease);
}

.navpanel__link span {
  font-size: 19px;
  font-weight: 500;
  letter-spacing: -0.015em;
}

.navpanel__link:hover { color: var(--ink); }
.navpanel__link:hover i { width: 22px; opacity: 1; }

/* Sections are a level below pages, so they are set smaller — the panel should
   not suggest that jumping within a page is the same move as changing page. */
.navpanel__link--sec { padding: 9px 0; }
.navpanel__link--sec span { font-size: 14.5px; font-weight: 500; }

.navpanel__link[aria-current="page"],
.navpanel__link.is-current { color: var(--ink); }
.navpanel__link[aria-current="page"] i,
.navpanel__link.is-current i { width: 26px; opacity: 1; background: var(--accent); }

/* ---- foot ---- */

.navpanel__foot {
  padding-top: clamp(18px, 2.4vh, 26px);
  border-top: 1px solid var(--hairline-soft);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 16px;
}

.navpanel__mail {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--muted);
  transition: color 300ms var(--ease);
}
.navpanel__mail:hover { color: var(--accent-hover); }

@media (prefers-reduced-motion: reduce) {
  .navpanel__sheet, .navpanel__scrim, .navpanel__link,
  .navpanel__link i, .navtrig__bars i { transition: none; }
}

/* ---------------------------------------------------------------- 6. BUTTONS */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  height: 52px;
  padding: 0 30px;
  border: 1px solid transparent;
  border-radius: var(--radius-s);
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0;
  cursor: pointer;
  white-space: nowrap;
  transition: background 350ms var(--ease), border-color 350ms var(--ease),
              color 350ms var(--ease), transform 350ms var(--ease), opacity 350ms var(--ease);
}

.btn:active { transform: translateY(1px) scale(0.985); }

/* Primary — theme-inverse block, like the reference "Add to cart" */
.btn--primary,
.btn--brass {          /* v1 alias: brass buttons become primary outside the hero */
  background: var(--ink);
  border-color: var(--ink);
  color: var(--bg);
}
.btn--primary:hover, .btn--brass:hover { opacity: 0.82; }

.btn--ghost,
.btn--outline-ink {
  background: transparent;
  border-color: var(--hairline);
  color: var(--ink);
}
.btn--ghost:hover, .btn--outline-ink:hover { border-color: var(--ink); }

/* Was styled for a navy ground; the closing band is now page-coloured, so this
   is simply the outline button and stays an alias rather than a second look. */
.btn--outline-sand {
  background: transparent;
  border-color: var(--hairline);
  color: var(--ink);
}
.btn--outline-sand:hover { border-color: var(--ink); }

.section--inverse .btn--primary,
.section--navy .btn--brass {
  background: var(--accent);
  border-color: var(--accent);
  color: #1B2A41;
}
.section--inverse .btn--primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }

.btn--full { width: 100%; }

/* Quiet text link */
.link-brass,
.link-quiet {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  border: 0;
  border-bottom: 1px solid var(--hairline);
  padding: 0 0 3px;
  background: none;
  cursor: pointer;
  transition: border-color 350ms var(--ease), gap 350ms var(--ease), opacity 350ms var(--ease);
}
.link-brass:hover, .link-quiet:hover { border-color: var(--ink); gap: 12px; }

/* The closing band is page-coloured now, so a quiet link needs nothing special
   to be legible there — only the hairline it shares with every other one. */
.section--inverse .link-quiet,
.closing__alt {
  color: var(--ink);
  border-bottom-color: var(--hairline);
}
.section--inverse .link-quiet:hover,
.closing__alt:hover { color: var(--accent); border-color: var(--accent); }

/* Sits under the single primary action rather than beside it. */
.closing__alt { margin-top: 18px; }

/* Hero actions sit on the film, not on the page, so they take the hero's own
   palette: a cream block reads on lit sand where gold-on-gold would not. */
.hero .btn--primary {
  background: var(--hero-ink);
  border-color: var(--hero-ink);
  color: #2B1D0F;
  height: 54px;
  border-radius: var(--radius-s);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
}
.hero .btn--primary:hover { background: #FFFFFF; border-color: #FFFFFF; opacity: 1; }

.hero .link-quiet {
  color: var(--hero-ink);
  border-bottom-color: var(--hero-hairline);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0;
}
.hero .link-quiet:hover { color: var(--hero-accent); border-color: var(--hero-accent); }

/* ------------------------------------------------------------------ 7. HERO */
/* Full-bleed desert film — a real loaded caravan on lit dunes, which is what
   the drawn scene was standing in for. Two separately reframed cuts (16:9 and
   9:16) with one picked on the client, so a phone never pulls the wide one;
   the poster carries first paint and the video crossfades over it. The hero
   keeps its own fixed cream-on-sand palette: the footage does not follow the
   theme, so neither does the type sitting on it. */

.hero {
  --hero-ink:      #FDF6EA;
  --hero-ink-soft: rgba(253, 246, 234, 0.86);
  --hero-muted:    rgba(253, 246, 234, 0.66);
  --hero-hairline: rgba(253, 246, 234, 0.30);
  --hero-accent:   #E3C07A;   /* brand gold, lifted to read on lit sand */

  /* The window the film opens from — one set of values, animated once, read by
     the clip, the frame and the caption alike. Top and bottom are not equal:
     the plate sits two points above true centre, which is where the eye reads
     "centred". */
  --wt: 0%;    /* window inset: top    */
  --wb: 0%;    /* window inset: bottom */
  --wx: 0%;    /* window inset: sides  */
  --wr: 0px;   /* window corner radius */
  --win-open: 940ms;
  /* How long the film holds alone before the first line of type arrives,
     counted from the window starting to open. The whole copy entrance hangs
     off this one number. */
  --copy-lead: 900ms;

  position: relative;
  width: 100%;
  /* Divided by --zoom because the hero has to fill the screen: 100vh resolves
     against the real viewport and is then scaled to 90%, which would leave a
     tenth of the window showing the page behind it. */
  min-height: calc(100vh / var(--zoom));
  min-height: calc(100svh / var(--zoom));   /* svh: no jump when mobile browser chrome collapses */
  overflow: hidden;
  isolation: isolate;
  /* Ivory, not the film's brown: during the intro this is the ground the
     window sits on. Once open, the film covers it entirely. */
  background: var(--bg);
  color: var(--hero-ink);
}

/* ---- The opening ----------------------------------------------------------
   The film is full-bleed and full-size throughout; only the clip moves. That
   is what keeps the camels from growing or squashing as the window opens —
   the frame opens around them, like an aperture rather than a box being
   scaled up. `.ac-intro` is set by an inline head script before first paint
   and removed when the intro plays out, so the settled state below is also
   the no-JS state.

   Registering the four inset values makes them interpolable, which buys the
   thing a uniform clip-path transition cannot: the sides can travel on a
   different curve to the top and bottom. The plate widens first and drops its
   letterbox a beat later — an aperture opening, not a box inflating.        */

@property --wt { syntax: "<length-percentage>"; inherits: true; initial-value: 0%; }
@property --wb { syntax: "<length-percentage>"; inherits: true; initial-value: 0%; }
@property --wx { syntax: "<length-percentage>"; inherits: true; initial-value: 0%; }
@property --wr { syntax: "<length>"; inherits: true; initial-value: 0px; }

.hero {
  transition: --wx calc(var(--win-open) * 0.82) var(--ease),
              --wt var(--win-open) 90ms var(--ease),
              --wb var(--win-open) 90ms var(--ease),
              --wr calc(var(--win-open) * 0.7) var(--ease);
}

.ac-intro .hero {
  --wt: 17%;
  --wb: 21%;
  --wx: 19%;
  --wr: var(--radius);   /* the tile radius the rest of the site is built on */
  transition: none;      /* the window is the starting state, not a destination */
}

.hero__stage {
  position: absolute;
  inset: 0;
  clip-path: inset(var(--wt) var(--wx) var(--wb) var(--wx) round var(--wr));
}

/* The plate arrives rather than simply being there: a short rise and fade,
   under half a second so the poster still lands as the LCP element early. */
.ac-intro .hero__stage {
  opacity: 0;
  transform: translate3d(0, 14px, 0);
  transition: opacity 420ms var(--ease), transform 620ms var(--ease);
}
.ac-intro.ac-mounted .hero__stage { opacity: 1; transform: none; }

/* Nothing is written on the plate, so it shows clean footage; the scrim
   arrives with the copy that actually needs it. */
.ac-intro .hero__scrim { opacity: 0; transition: none; }

/* Shadow, hairline and registration ticks can't live on the clipped element —
   the clip would cut them off — so an empty box tracks the same rect. It needs
   no transition of its own: it reads the same animating values as the clip. */
.hero__frame {
  position: absolute;
  inset: var(--wt) var(--wx) var(--wb) var(--wx);
  z-index: 2;
  pointer-events: none;
  border-radius: var(--wr);
  /* One deep, soft shadow rather than the two-layer "material card" stack —
     this is a plate resting on sand, not a floating panel. Declared here, not
     under .ac-intro, so removing the intro class doesn't snap the shadow off
     a frame that is still fading. */
  box-shadow: 0 60px 120px -60px rgba(27, 42, 65, 0.5);
  outline: 1px solid rgba(27, 42, 65, 0.12);
  outline-offset: -1px;
  opacity: 0;
  transition: opacity 460ms var(--ease);   /* the exit */
}

.ac-intro.ac-mounted .hero__frame {
  opacity: 1;
  transition: opacity 520ms var(--ease) 60ms;   /* the entrance */
}

/* Warm light pooling around the plate, so it sits in the ivory rather than
   floating on it. A halo, not a fill — the centre stays clear or it would
   tint the footage it is meant to sit behind. */
.hero__frame::before {
  content: "";
  position: absolute;
  inset: -16% -10%;
  z-index: -1;
  background: radial-gradient(closest-side,
    transparent 58%, var(--accent-soft) 80%, transparent 100%);
  filter: blur(34px);
}

/* Crop marks: the register this trade already reads on every document it
   touches. They sit outside the trim, on the ivory — a gold hairline laid over
   the footage itself would simply disappear into it. Gone the moment the plate
   opens, along with the rest of the frame. */
.hero__frame i {
  position: absolute;
  width: 13px;
  height: 13px;
  border: 0 solid var(--accent);
  opacity: 0.85;
}
.hero__frame i:nth-child(1) { top: -17px; left: -17px;  border-top-width: 1.5px; border-left-width: 1.5px; }
.hero__frame i:nth-child(2) { top: -17px; right: -17px; border-top-width: 1.5px; border-right-width: 1.5px; }
.hero__frame i:nth-child(3) { bottom: -17px; left: -17px;  border-bottom-width: 1.5px; border-left-width: 1.5px; }
.hero__frame i:nth-child(4) { bottom: -17px; right: -17px; border-bottom-width: 1.5px; border-right-width: 1.5px; }

/* Caption under the plate, in the same mono voice as the coordinate readout
   it hands over to. It rides the bottom inset down and out as the plate
   opens, so the line leaves with the frame instead of blinking off. */
.hero__plate {
  position: absolute;
  z-index: 3;
  left: 0; right: 0;
  top: calc(100% - var(--wb) + 20px);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
  opacity: 0;
  pointer-events: none;
  transition: opacity 300ms var(--ease);   /* the exit, as the plate opens */
}
.hero__plate::before {
  content: "";
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--accent);
}
.ac-intro.ac-mounted .hero__plate {
  opacity: 1;
  transition: opacity 600ms var(--ease) 220ms;
}

/* Skipped intro (a scroll, a click, a key): finish fast rather than cut. The
   copy lead collapses with the window — someone who just told us they are done
   watching should not then wait out the lead. */
.hero--fast { --win-open: 300ms; --copy-lead: 60ms; }

/* Nothing is drawn over the film any more. The live sand canvases (a
   multiplied suspension veil and a screened grain layer) and the pointer
   parallax that moved them are both gone — "remove any effect from the video".
   The plate plays as rendered.

   The scrim below is NOT in that category and stays: it is what makes cream
   type legible on lit sand, and the client's note was that the text is
   positioned correctly, which it only is because of it. */

/* Where the film becomes the page. The v4 master opens and closes inside a
   sand whiteout — pale, low-contrast, only a few shades off the ivory ground —
   so the two surfaces can actually be dissolved into each other rather than
   butted together and covered with a gradient.

   Two things make this read as a melt instead of a band:

   1. A hand-eased ramp. A two-stop linear gradient banks all its change in the
      middle and leaves a visible horizontal seam at each end; these stops
      approximate an ease-in-out, so the ivory arrives without an edge. On an
      8-bit gradient this long, banding is the failure mode, and a curve that
      never runs flat is what hides it.
   2. It rises off the floor. 18vh was a hem on the bottom of the film. At
      ~30vh the last third of the frame is already becoming page while the
      caravan is still walking through it, which is the actual handover.

   Deep enough to reach the copy, so the scrim underneath keeps the headline
   off it — see .hero__scrim, which is weighted to the same corner. */
.hero__fade {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: clamp(190px, 30vh, 360px);
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(to bottom,
    rgb(from var(--bg) r g b / 0)     0%,
    rgb(from var(--bg) r g b / 0.04)  12%,
    rgb(from var(--bg) r g b / 0.14)  26%,
    rgb(from var(--bg) r g b / 0.32)  41%,
    rgb(from var(--bg) r g b / 0.56)  56%,
    rgb(from var(--bg) r g b / 0.78)  70%,
    rgb(from var(--bg) r g b / 0.93)  83%,
    rgb(from var(--bg) r g b / 1)     94%);
  transition: opacity var(--win-open) var(--ease);
}

/* relative-colour syntax is 2023+; older engines get the original two-stop
   ramp rather than no fade at all, which would end the film on a hard edge. */
@supports not (color: rgb(from white r g b / 1)) {
  .hero__fade { background: linear-gradient(to bottom, transparent 0%, var(--bg) 94%); }
}

/* During the window beat the ground already IS the page — the melt only has
   meaning once the film goes full-bleed. */
.ac-intro .hero__fade { opacity: 0; transition: none; }

/* .hero__media held the JS pointer parallax and .hero__layer the scroll-driven
   exit — separate elements because an animation always beats an inline style.
   The parallax is gone, so this is now a plain wrapper; the will-change came
   off with it rather than promoting a layer nothing transforms. */
.hero__media { position: absolute; inset: 0; }
.hero__layer { position: absolute; inset: 0; }

.hero__poster,
.hero__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* The drone shot sits low in frame — bias the crop upward so the caravan
     survives on short and ultra-wide viewports. Re-check if the hero height
     changes. */
  object-position: center 42%;
}

.hero__video { opacity: 0; transition: opacity 700ms var(--ease); }
.hero__video.is-ready { opacity: 1; }

/* Load-bearing, not decoration: the plate is bright sand edge to edge and
   cream type is illegible without it. Weighted bottom-left, which also keeps
   copy clear of the generation watermark in the lower right. Check any change
   against the sunlit-camel frame — the brightest in the clip. */
.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  transition: opacity var(--win-open) var(--ease);
  background:
    linear-gradient(to top,
      rgba(28, 18, 8, 0.86) 0%,
      rgba(28, 18, 8, 0.48) 34%,
      rgba(28, 18, 8, 0.08) 64%,
      rgba(28, 18, 8, 0.34) 100%),
    linear-gradient(to right,
      rgba(28, 18, 8, 0.58) 0%,
      rgba(28, 18, 8, 0.14) 46%,
      rgba(28, 18, 8, 0) 72%);
}

.hero__content {
  position: absolute;
  z-index: 3;
  /* Aligned to the content edge, not the window edge. Was left/right:
     var(--gutter), which put the headline flush with the viewport while the
     bar above it and every section below it were centred in --measure — three
     different left edges on any window wider than 1560px. */
  left: calc(max(0px, 100% - var(--measure)) / 2 + var(--gutter));
  right: calc(max(0px, 100% - var(--measure)) / 2 + var(--gutter));
  bottom: clamp(86px, 13vh, 152px);
  /* Wide enough for the headline to break where it is written to break. The
     copy under it keeps its own 46ch measure, so this only ever gives the two
     display lines the room they were authored for. */
  max-width: 58rem;
}

/* Cream on a photograph — the page focus ring (navy) would vanish here. */
.hero :focus-visible { outline-color: var(--hero-ink); }

.hero__meta {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 26px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--hero-muted);
}

.hero__dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--hero-accent);
  flex: 0 0 auto;
  position: relative;
}
.hero__dot::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: var(--hero-accent);
  animation: dotPing 2.8s var(--ease) infinite;
}
@keyframes dotPing {
  0%   { transform: scale(1); opacity: 0.7; }
  70%  { transform: scale(3.6); opacity: 0; }
  100% { transform: scale(3.6); opacity: 0; }
}

.hero__coord { font-variant-numeric: tabular-nums; white-space: nowrap; }
.hero__sep { width: 18px; height: 1px; background: var(--hero-hairline); flex: 0 0 auto; }

.hero__title {
  margin: 0 0 26px;
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: clamp(38px, 5.1vw, 74px);
  line-height: 1.02;
  letter-spacing: -0.042em;
  color: var(--hero-ink);
  text-shadow: 0 2px 30px rgba(20, 12, 4, 0.38);
}

.hero__title .line {
  display: block;
  overflow: hidden;
  padding-bottom: 0.06em;
  margin-bottom: -0.06em;
}
.hero__title .line > span {
  display: block;
  transform: translateY(110%);
  transition: transform 1100ms var(--ease);
}
/* Measured from the moment the window starts opening. Every delay hangs off
   --copy-lead so the whole entrance moves as one: raise it and the film holds
   the screen alone for longer before any type arrives. The offsets after it
   are the internal rhythm and stay put.

   It has to be a variable rather than six hardcoded numbers because a reader
   who skips the intro gets --copy-lead collapsed (see .hero--fast) — otherwise
   they skip the window and then sit through a second of empty film. */
.hero--revealed .hero__title .line > span { transform: translateY(0); }
.hero--revealed .hero__title .line:nth-child(1) > span { transition-delay: calc(var(--copy-lead) + 200ms); }
.hero--revealed .hero__title .line:nth-child(2) > span { transition-delay: calc(var(--copy-lead) + 360ms); }

.hero__title .l1 { color: var(--hero-ink); }
.hero__title .l2 { color: var(--hero-accent); }

.hero__copy {
  font-size: 16px;
  line-height: 1.7;
  color: var(--hero-ink-soft);
  max-width: 46ch;
  /* The copy is the last thing in the block now — the button that used to
     follow it is gone, and its trailing margin went with it. */
  margin-bottom: 0;
  text-shadow: 0 1px 18px rgba(20, 12, 4, 0.42);
}

.hero__content .reveal { opacity: 0; transform: translateY(18px); }

.hero--revealed .hero__content .reveal {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 800ms var(--ease), transform 800ms var(--ease);
}
.hero--revealed .hero__content .reveal:nth-child(1) { transition-delay: var(--copy-lead); }
.hero--revealed .hero__content .reveal:nth-child(3) { transition-delay: calc(var(--copy-lead) + 700ms); }

/* Invisible copy is still clickable — keep the window beat inert. */
.ac-intro .hero__content, .ac-intro .hero__cue { pointer-events: none; }

/* The way out of the hero, and now a real control: it is a <button> that
   scrolls to what we trade, because the catalogue arrives by scrolling rather
   than by leaving for another page.

   It was a horizontal dash lying beside the word, which pointed along the
   bottom edge rather than down and read as a stray rule once the button above
   it was removed. It is one vertical gesture now — label, then a rail with a
   light falling down it — set on the same left axis as the logo, the meta line
   and the headline, so the hero has a single left margin from the bar to the
   fold.                                                                     */
.hero__cue {
  position: absolute;
  z-index: 3;
  left: calc(max(0px, 100% - var(--measure)) / 2 + var(--gutter));
  bottom: 26px;
  appearance: none;
  background: none;
  border: 0;
  padding: 0 10px 0 0;
  cursor: pointer;
  display: grid;
  justify-items: start;
  gap: 11px;
  text-align: left;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  /* The cue sits inside the bottom fade, where the ground is already the
     page — so it reads in page ink, not the hero's cream. */
  color: var(--muted);
  opacity: 0;
  transition: color 400ms var(--ease);
}
.hero--revealed .hero__cue {
  opacity: 1;
  transition: opacity 900ms var(--ease) calc(var(--copy-lead) + 1350ms),
              color 400ms var(--ease);
}
.hero__cue:hover { color: var(--ink); }

/* The rail. overflow:hidden is what makes the falling light appear out of the
   top of it and vanish out of the bottom instead of floating past its ends. */
.hero__cue i {
  position: relative;
  display: block;
  width: 1.5px;
  height: 46px;
  overflow: hidden;
  border-radius: 1px;
  background: linear-gradient(to bottom, rgba(176, 141, 87, 0.40), rgba(176, 141, 87, 0.05));
  background: linear-gradient(to bottom,
              color-mix(in srgb, var(--accent) 40%, transparent),
              color-mix(in srgb, var(--accent) 5%, transparent));
  transition: height 480ms var(--ease);
}
.hero__cue i::after {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 1.5px;
  height: 42%;
  border-radius: 1px;
  background: linear-gradient(to bottom, transparent, var(--accent));
  animation: cueFall 2.8s var(--ease) infinite;
}
.hero__cue:hover i { height: 62px; }

@keyframes cueFall {
  0%   { transform: translateY(-100%); opacity: 0; }
  25%  { opacity: 1; }
  75%  { opacity: 1; }
  100% { transform: translateY(240%);  opacity: 0; }
}

/* Reduced motion: no intro, no film — the poster stays and the copy is simply
   there. The head script and the JS make the same call; this is the backstop
   for when scripts fail or run late. */
@media (prefers-reduced-motion: reduce) {
  .hero__video { display: none; }
  .hero__dot::after, .hero__cue i, .hero__cue i::after { animation: none; }
  .hero__stage { clip-path: none; }
  .hero__frame { display: none; }
}

@media (max-height: 820px) and (min-width: 769px) {
  .hero__title { font-size: clamp(32px, 4vw, 54px); margin-bottom: 22px; }
  .hero__meta { margin-bottom: 20px; }
  .hero__copy { font-size: 15px; margin-bottom: 0; }
  .hero__content { bottom: clamp(72px, 10vh, 110px); }
}

@media (max-height: 680px) and (min-width: 769px) {
  .hero__title { font-size: clamp(28px, 3.2vw, 42px); }
  .hero__copy { display: none; }
  .hero__content { bottom: 64px; }
}

/* ---- 7a. HERO REDESIGN — text-mask exploration ----------------------------
   Overrides the bottom-left editorial layout with a centred, screen-filling
   headline that uses the poster image as a text fill on line 1 and an outlined
   gold stroke on line 2. The video plays behind; the letters themselves become
   the frame. Reverts cleanly by removing this block. */

.hero__content {
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  max-width: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 0 var(--gutter);
  text-align: center;
}

.hero__title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(56px, 10vw, 160px);
  line-height: 0.92;
  letter-spacing: -0.04em;
  margin-bottom: clamp(20px, 3vh, 40px);
  text-shadow: none;
}

.hero__title .l1 {
  background: url('../hero/v7/poster-desktop.jpg') center 38% / cover no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  filter: brightness(1.25) contrast(1.1);
}

.hero__title .l2 {
  color: var(--hero-accent);
  -webkit-text-fill-color: var(--hero-accent);
}

.hero__meta {
  justify-content: center;
}

.hero__copy {
  max-width: 46ch;
  margin: 0 auto;
  text-align: center;
  text-shadow: 0 2px 24px rgba(10, 6, 2, 0.6);
}

.hero__scrim {
  background:
    radial-gradient(ellipse 100% 80% at 50% 35%,
      rgba(210, 185, 130, 0.08) 0%,
      transparent 60%),
    radial-gradient(ellipse 130% 100% at center 50%,
      rgba(28, 18, 8, 0.05) 0%,
      rgba(28, 18, 8, 0.38) 55%,
      rgba(28, 18, 8, 0.62) 100%),
    linear-gradient(to top,
      rgba(28, 18, 8, 0.52) 0%,
      rgba(28, 18, 8, 0.08) 40%,
      transparent 100%);
}

.hero__cue {
  left: 50%;
  transform: translateX(-50%);
  justify-items: center;
  text-align: center;
}

.hero__title .line {
  overflow: visible;
}

/* Short viewports — scale the mask title down */
@media (max-height: 820px) and (min-width: 769px) {
  .hero__title { font-size: clamp(40px, 7vw, 100px); }
}
@media (max-height: 680px) and (min-width: 769px) {
  .hero__title { font-size: clamp(36px, 6vw, 80px); }
  .hero__copy { display: none; }
}

/* Mobile — tighter sizing, keep the effect */
@media (max-width: 768px) {
  .hero__title {
    font-size: clamp(44px, 12vw, 72px);
    line-height: 0.95;
    letter-spacing: -0.035em;
  }
  .hero__title .l1 {
    background-image: url('../hero/v7/poster-mobile.jpg');
    background-position: center 50%;
  }
}

/* ---- 7a-ii. CINEMATIC EFFECTS — mouse gradient + noise overlay ---------- */

.hero__glow {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    radial-gradient(
      ellipse 900px 700px at calc(var(--mx, 0.5) * 100%) calc(var(--my, 0.5) * 100%),
      rgba(200, 169, 126, 0.16), transparent 55%),
    radial-gradient(
      ellipse 650px 450px at calc(var(--mx, 0.5) * 80% + 20%) calc(var(--my, 0.5) * 70% + 30%),
      rgba(215, 165, 100, 0.10), transparent 45%);
  mix-blend-mode: screen;
  transition: opacity 600ms var(--ease);
}

.noise-overlay {
  position: fixed;
  inset: 0;
  z-index: 9998;
  pointer-events: none;
  mix-blend-mode: overlay;
  opacity: 0.045;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='256' height='256'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-repeat: repeat;
  background-size: 256px 256px;
  animation: noise-drift 20s ease-in-out infinite alternate;
}

@keyframes noise-drift {
  from { background-position: 0 0; }
  to   { background-position: 100px 100px; }
}

@media (prefers-reduced-motion: reduce) {
  .noise-overlay { display: none; }
  .hero__glow { display: none; }
}

/* --------------------------------------------------- 7b. CORRIDOR STRIP */
/* The trade route as a quiet rail directly under the hero — the waypoints
   the client's caravan actually travels. */

.corridor-strip {
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
  background: var(--bg);
}

.corridor-strip ol {
  display: flex;
  align-items: center;
  gap: clamp(18px, 3vw, 44px);
  padding: 16px 0;
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}

.corridor-strip li { display: flex; align-items: center; gap: clamp(18px, 3vw, 44px); white-space: nowrap; }

.corridor-strip li:not(:last-child)::after {
  content: "";
  width: 3px; height: 3px;
  border-radius: 50%;
  background: var(--accent);
  flex: 0 0 auto;
}

@media (max-width: 768px) {
  /* Scrolls rather than wrapping, so the route still reads as one line. */
  .corridor-strip ol {
    overflow-x: auto;
    flex-wrap: nowrap;
    scrollbar-width: none;
    gap: 16px;
  }
  .corridor-strip ol::-webkit-scrollbar { display: none; }
  .corridor-strip li { gap: 16px; }
}

/* ------------------------------------------------------------ 8. PRESS STRIP */
/* Five accreditations in one static centred row. Nothing moves — see the note
   on .press__row for why the marquee that used to be here went.        */

.press { padding: clamp(56px, 6vw, 96px) 0; text-align: center; }

.press__tag { display: block; margin-bottom: clamp(28px, 3vw, 44px); }

/* Static row. The marquee (a duplicated track sliding on a keyframe, masked at
   both edges, paused on hover) is gone with the animation: four real
   accreditations moving across the screen read as an ad, and the loop forced
   the visible row to be aria-hidden with a duplicate hidden list beside it for
   screen readers. A row that does not move needs neither. */
.press__row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: clamp(20px, 4vw, 64px);
}

.press__mark {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(20px, 2.2vw, 30px);
  letter-spacing: 0.22em;
  color: var(--ink);
  opacity: 0.85;
  white-space: nowrap;
}

/* One treatment for all five now. The alternating weights and tracking existed
   to keep a long repeating track from looking like wallpaper; in a row of five
   they just read as inconsistent. */


/* --------------------------------------------------------------- 9. TAB RAIL */

.tabs { display: flex; align-items: baseline; gap: clamp(16px, 2vw, 28px); flex-wrap: wrap; }

.tabs__btn {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-size: clamp(22px, 2.4vw, 32px);
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--muted);
  opacity: 0.55;
  transition: color 350ms var(--ease), opacity 350ms var(--ease);
}

.tabs__btn:hover { opacity: 0.85; }
.tabs__btn.is-active { color: var(--ink); opacity: 1; }
.tabs__btn.is-active::after { content: ""; display: block; height: 2px; margin-top: 6px; background: var(--accent); }

/* ----------------------------------------------------------- 10. PRODUCT CARD */

.grid-products {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(16px, 1.6vw, 26px);
  row-gap: clamp(36px, 3.4vw, 52px);
}

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  color: inherit;
}

.card__figure {
  position: relative;
  aspect-ratio: 4 / 4.2;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--tile);
  margin-bottom: 16px;
  transition: background 400ms var(--ease);
}

.card:hover .card__figure { background: var(--tile-strong); }

.card__art {
  position: absolute;
  inset: 12%;
  width: 76%; height: 76%;
  object-fit: contain;
  margin: auto;
  transition: transform 900ms var(--ease);
}

.card:hover .card__art { transform: scale(1.04) translateY(-1%); }

/* The metrics block runs to two or three lines depending on the commodity, so
   the Spec PDF / RFQ row landed at seven different heights across sixteen
   cards and the grid read as broken. The card is a column; the metrics take
   the slack and the actions are pushed to a common baseline. */
.card { height: 100%; }
.card__metrics { flex: 1 1 auto; }
.card__actions { margin-top: auto; }

/* A photograph is not a placeholder. The line art floats inset on the sand
   tile because it is a drawing of the commodity; a real photograph of the
   cargo fills the tile edge to edge — build_photos.py has already cropped it
   to exactly this shape, so cover crops nothing. */
.card__art--photo {
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}

/* The badge and the Quick RFQ button already sit on solid --bg. The heart does
   not — it is bare muted stroke, which disappears over grain. */
.card__figure:has(.card__art--photo) .card__fav {
  background: var(--bg);
  border-radius: 50%;
  color: var(--ink);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
}

/* Badge — top-left pill */
.card__badge {
  position: absolute;
  top: 14px; left: 14px;
  z-index: 3;
  padding: 5px 11px;
  border-radius: var(--radius-s);
  background: var(--bg);
  border: 1px solid var(--accent);
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 10px;
  font-weight: 700;
}

.card__badge--sale { color: var(--accent); border-color: var(--accent); }

/* Wishlist heart — top-right */
.card__fav {
  position: absolute;
  top: 12px; right: 12px;
  z-index: 3;
  appearance: none;
  background: none;
  border: 0;
  cursor: pointer;
  width: 36px; height: 36px;
  display: grid;
  place-items: center;
  color: var(--muted);
  transition: color 300ms var(--ease), transform 300ms var(--ease);
}

.card__fav:hover { color: var(--ink); transform: scale(1.08); }
.card__fav svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.5; }
.card__fav.is-active { color: var(--badge-sale); }
.card__fav.is-active svg { fill: currentColor; }

/* Quick RFQ — slides up on hover, always visible on touch */
.card__quick {
  position: absolute;
  z-index: 3;
  left: 12px; right: 12px; bottom: 12px;
  appearance: none;
  border: 0;
  cursor: pointer;
  height: 44px;
  border-radius: var(--radius-s);
  background: var(--bg);
  color: var(--ink);
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 350ms var(--ease), transform 350ms var(--ease), background 300ms var(--ease);
}

.card:hover .card__quick, .card__quick:focus-visible { opacity: 1; transform: translateY(0); }
.card__quick:hover { background: var(--ink); color: var(--bg); }
.card__quick:active { transform: scale(0.98); }

@media (hover: none) {
  .card__quick { opacity: 1; transform: none; }
}

/* Name / price row */
.card__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
}

.card__title { font-size: 16px; font-weight: 600; letter-spacing: -0.01em; color: var(--ink); }

.card__price {
  font-size: 14.5px;
  font-weight: 500;
  color: var(--muted);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.card__cat {
  margin-top: 5px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Whole card navigates via a stretched link, so the PDF download and the RFQ
   button can stay real, focusable elements rather than nested anchors. */
.card__link { color: inherit; }
.card__link::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
}
.card__link:hover { color: var(--accent); }

.card__grade {
  display: block;
  margin-top: 5px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
}

/* The brief's key technical specs, on the card itself. */
.card__metrics {
  margin-top: 12px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.card__actions {
  position: relative;
  z-index: 1;
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--hairline-soft);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

.btn--sm {
  height: 38px;
  padding: 0 16px;
  font-size: 12px;
  letter-spacing: 0.04em;
}

.card__rfq { font-size: 13px; }

/* Hidden metric block (used by drawer template) */
.card__spec { display: none; }

/* ------------------------------------------------------- 11. CATEGORY CARDS */

.grid-cats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(16px, 1.6vw, 26px);
}

.catcard {
  position: relative;
  display: block;
  aspect-ratio: 3 / 4;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--tile);
  transition: background 400ms var(--ease);
}

.catcard:hover { background: var(--tile-strong); }

.catcard__art {
  position: absolute;
  inset: 14% 14% 20%;
  width: 72%; height: 66%;
  margin: auto;
  object-fit: contain;
  transition: transform 900ms var(--ease);
}

.catcard:hover .catcard__art { transform: scale(1.05); }

.catcard__art--photo {
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}

/* The count is bare muted type — fine on flat sand, invisible on grain. Give
   it a ground only on the tiles that carry a photograph. */
.catcard:has(.catcard__art--photo) .catcard__count {
  padding: 3px 8px;
  border-radius: var(--radius-s);
  background: var(--bg);
  color: var(--ink);
}

.catcard__label {
  position: absolute;
  left: 14px; right: 14px; bottom: 14px;
  height: 46px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-s);
  background: var(--bg);
  color: var(--ink);
  font-size: 13.5px;
  font-weight: 600;
  text-align: center;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
  transition: background 300ms var(--ease), color 300ms var(--ease);
}

.catcard:hover .catcard__label { background: var(--ink); color: var(--bg); }

.catcard__count {
  position: absolute;
  top: 14px; left: 14px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.12em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------- 12. SHOP PAGE */

.pagehead {
  padding: calc(var(--nav-h) + clamp(48px, 6vw, 96px)) 0 clamp(36px, 4vw, 56px);
}

.pagehead__tag { display: block; margin-bottom: 20px; }
.pagehead__title { max-width: 20ch; }
.pagehead__copy { margin-top: 20px; max-width: 52ch; font-size: 16px; line-height: 1.7; color: var(--muted); }

/* ------------------------------------------------------------- PAGE HERO -- */
/* Every interior page used to open the same way: eyebrow, heading, one
   paragraph, flat sand, straight under the nav. No anchor, and nothing to hold
   the eye. This is the landing page's opening at a quieter volume — a still
   from the same film, the same melt into the page ground, the same cream type
   over it — so an interior page reads as the same site rather than as the
   template the site was built from.

   The stills come from build_stills.py; framing is per page via --still-pos
   rather than a pre-cropped export, so re-framing costs one declaration. */

.pagehero {
  position: relative;
  isolation: isolate;
  min-height: clamp(340px, 46vh, 540px);
  display: flex;
  align-items: flex-end;
  /* Bottom padding clears the melt: .pagehero__fade runs up to 190px tall and
     a three-line standfirst was disappearing into it. */
  padding: calc(var(--nav-h) + clamp(40px, 5vw, 72px)) 0 clamp(58px, 6vw, 96px);
  overflow: hidden;
}

.pagehero__media { position: absolute; inset: 0; z-index: -2; }

.pagehero__media img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: var(--still-pos, 50% 42%);
}

/* Two jobs, two layers. The scrim is weighted to the corner the copy sits in,
   so the type has ground without the whole photograph going grey. The melt is
   the hero's own gradient — see .hero__fade — so the band ends the way the
   film does instead of stopping on a hard edge. */
.pagehero__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    /* Under the bar. .nav--over paints the mark and the links cream, and these
       stills are bright haze at the top where the hero film is dark — without
       this the header is a cream lockup on pale sand and effectively invisible.
       Short and weak: it gives the bar ground without greying the sky. */
    linear-gradient(to bottom, rgba(20, 14, 6, 0.46) 0%, rgba(20, 14, 6, 0.18) 15%, rgba(20, 14, 6, 0) 31%),
    linear-gradient(to top, rgba(20, 14, 6, 0.62) 0%, rgba(20, 14, 6, 0.28) 34%, rgba(20, 14, 6, 0) 68%),
    linear-gradient(to right, rgba(20, 14, 6, 0.42) 0%, rgba(20, 14, 6, 0.10) 46%, rgba(20, 14, 6, 0) 76%);
}

.pagehero__fade {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: clamp(90px, 16vh, 190px);
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(to bottom,
    rgb(from var(--bg) r g b / 0)    0%,
    rgb(from var(--bg) r g b / 0.10) 24%,
    rgb(from var(--bg) r g b / 0.34) 48%,
    rgb(from var(--bg) r g b / 0.72) 74%,
    rgb(from var(--bg) r g b / 1)   100%);
}

@supports not (color: rgb(from #fff r g b / 1)) {
  .pagehero__fade { background: linear-gradient(to bottom, transparent 0%, var(--bg) 94%); }
}

.pagehero__inner { position: relative; width: 100%; }

/* Cream type on a photograph, exactly as the hero sets it — the film keeps its
   own fixed palette and never re-tints with the theme. */
.pagehero__tag { display: block; margin-bottom: 18px; color: #E8C489; }

.pagehero__title {
  max-width: 22ch;
  color: #FDF6EA;
  text-wrap: balance;
  text-shadow: 0 1px 26px rgba(20, 14, 6, 0.34);
}

.pagehero__copy {
  margin-top: 18px;
  max-width: 54ch;
  font-size: clamp(15px, 1.2vw, 17px);
  line-height: 1.7;
  color: rgba(253, 246, 234, 0.82);
}

/* Per-page framing. The wide dune plates want their horizon high in the band;
   the portrait wants the handler off the copy. */
.pagehero--about     { --still-pos: 50% 34%; }
.pagehero--catalogue { --still-pos: 52% 46%; }
.pagehero--insights  { --still-pos: 50% 52%; }
.pagehero--contact   { --still-pos: 72% 32%; }
.pagehero--note      { --still-pos: 50% 54%; }

/* About keeps its own two-part display heading on the film. Two adjustments:
   --accent is mixed to sit on paper and goes muddy on a photograph, so the
   accent line comes up to the band's gold; and the heading's trailing margin
   exists to open the copy beneath it, which is now in the next section. */
.pagehero .ab-hero__title { margin-bottom: 0; max-width: 20ch; }
.pagehero .ab-hero__title em { color: #E8C489; }

@media (max-width: 768px) {
  .pagehero { min-height: clamp(300px, 42vh, 420px); }
  /* On a phone the copy occupies most of the band, so the scrim has to carry
     the whole width rather than just the leading corner. */
  .pagehero__scrim {
    background:
      linear-gradient(to bottom, rgba(20, 14, 6, 0.38) 0%, rgba(20, 14, 6, 0.12) 18%, rgba(20, 14, 6, 0) 34%),
      linear-gradient(to top, rgba(20, 14, 6, 0.68) 0%, rgba(20, 14, 6, 0.34) 46%, rgba(20, 14, 6, 0.06) 82%);
  }
}

.shop {
  display: grid;
  grid-template-columns: 240px 1fr;
  gap: clamp(32px, 4vw, 72px);
  align-items: start;
  padding-bottom: clamp(72px, 8vw, 120px);
}

.filters { position: sticky; top: calc(var(--nav-h) + 24px); }

.filters__title {
  font-size: 15px;
  font-weight: 600;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--hairline);
  margin-bottom: 4px;
}

.fgroup { border-bottom: 1px solid var(--hairline); padding: 16px 0 18px; }

.fgroup__head {
  width: 100%;
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 14.5px;
  font-weight: 600;
  color: var(--ink);
}

.fgroup__head svg { width: 12px; height: 12px; stroke: currentColor; transition: transform 350ms var(--ease); }
.fgroup.is-closed .fgroup__head svg { transform: rotate(180deg); }

.fgroup__body { padding-top: 14px; display: flex; flex-direction: column; gap: 10px; }
.fgroup.is-closed .fgroup__body { display: none; }

.check {
  display: flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
  font-size: 14px;
  color: var(--ink-soft);
}

.check input {
  appearance: none;
  width: 17px; height: 17px;
  margin: 0;
  border: 1.5px solid var(--hairline);
  border-radius: 4px;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: border-color 250ms var(--ease), background 250ms var(--ease);
  flex: 0 0 auto;
}

.check input:hover { border-color: var(--muted); }
.check input:checked { background: var(--accent); border-color: var(--accent); }
.check input:checked::after {
  content: "";
  width: 9px; height: 5px;
  border-left: 1.5px solid var(--bg);
  border-bottom: 1.5px solid var(--bg);
  transform: rotate(-45deg) translateY(-1px);
}

.check__n { margin-left: auto; font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* Sort row above grid */
.shop__toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  margin-bottom: 24px;
}

.shop__count { font-size: 13.5px; color: var(--muted); font-variant-numeric: tabular-nums; }

.sort {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  font-weight: 500;
  color: var(--ink);
}

.sort select {
  appearance: none;
  background: none;
  border: 0;
  cursor: pointer;
  font-weight: 600;
  color: var(--ink);
  padding-right: 18px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='7' viewBox='0 0 10 7'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23797C82' stroke-width='1.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right center;
}

.shop__empty {
  padding: 80px 0;
  text-align: center;
  color: var(--muted);
  display: none;
}
.shop__empty.is-visible { display: block; }

/* ----------------------------------------------------------------- 13. PDP */

.pdp {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: clamp(32px, 5vw, 88px);
  align-items: start;
  padding-bottom: clamp(72px, 8vw, 120px);
}

.pdp__stage {
  position: sticky;
  top: calc(var(--nav-h) + 24px);
  border-radius: var(--radius);
  background: var(--tile);
  aspect-ratio: 1 / 1.05;
  display: grid;
  place-items: center;
  overflow: hidden;
}

.pdp__stage img { width: 62%; height: 78%; object-fit: contain; }
.pdp__stage img.pdp__photo { width: 100%; height: 100%; object-fit: cover; }

.pdp__body { padding-top: 8px; }

.pdp__cat { display: block; margin-bottom: 14px; }
.pdp__title { font-size: clamp(30px, 3.2vw, 44px); font-weight: 600; letter-spacing: -0.025em; margin-bottom: 10px; }
.pdp__price { font-size: 17px; color: var(--muted); font-weight: 500; margin-bottom: 26px; }

.pdp__rule { height: 1px; background: var(--hairline); border: 0; margin: 0 0 26px; }

.pdp__desc { font-size: 15px; line-height: 1.75; color: var(--ink-soft); max-width: 54ch; margin-bottom: 32px; }

.pdp__opt-label { font-size: 14.5px; font-weight: 600; margin-bottom: 12px; }

.chips { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 30px; }

.chip {
  appearance: none;
  background: none;
  cursor: pointer;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-s);
  padding: 12px 18px;
  font-size: 14px;
  font-weight: 500;
  color: var(--ink-soft);
  transition: border-color 250ms var(--ease), color 250ms var(--ease), background 250ms var(--ease);
}

.chip:hover { border-color: var(--muted); }
.chip.is-active { border-color: var(--ink); color: var(--ink); font-weight: 600; }

.pdp__buy { display: flex; gap: 14px; margin-bottom: 26px; }

.qty {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-s);
  height: 52px;
}

.qty button {
  appearance: none;
  background: none;
  border: 0;
  cursor: pointer;
  width: 42px; height: 100%;
  font-size: 18px;
  color: var(--muted);
  transition: color 250ms var(--ease);
}
.qty button:hover { color: var(--ink); }

.qty input {
  width: 64px;
  border: 0;
  background: none;
  text-align: center;
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  -moz-appearance: textfield;
}
.qty input::-webkit-outer-spin-button, .qty input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

.pdp__buy .btn { flex: 1; }

.pdp__shiplink { margin-bottom: 40px; display: inline-block; }

/* Accordions */
.acc { border-top: 1px solid var(--hairline); }

.acc__item { border-bottom: 1px solid var(--hairline); }

.acc__head {
  width: 100%;
  appearance: none;
  background: none;
  border: 0;
  padding: 20px 0;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  font-size: 15.5px;
  font-weight: 600;
  color: var(--ink);
  text-align: left;
}

.acc__head svg { width: 14px; height: 14px; flex: 0 0 auto; stroke: currentColor; transition: transform 350ms var(--ease); }
.acc__item.is-open .acc__head svg { transform: rotate(45deg); }

.acc__body { display: none; padding: 0 0 24px; }
.acc__item.is-open .acc__body { display: block; }

/* ------------------------------------------------------------ 14. SPEC TABLE */

.spec {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.spec th[scope="col"] {
  text-align: left;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 0 0 12px;
  border-bottom: 1px solid var(--hairline);
}

.spec th[scope="col"]:last-child { text-align: right; }

.spec th[scope="row"] {
  text-align: left;
  font-size: 13.5px;
  font-weight: 400;
  color: var(--muted);
  padding: 12px 12px 12px 0;
  border-bottom: 1px solid var(--hairline-soft);
}

.spec td {
  text-align: right;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink);
  padding: 12px 0;
  border-bottom: 1px solid var(--hairline-soft);
  white-space: nowrap;
}

.spec tr:last-child th, .spec tr:last-child td { border-bottom: 0; }

/* --------------------------------------------------------------- 15. DRAWER */

.drawer-backdrop {
  position: fixed;
  inset: 0;
  z-index: 190;
  background: rgba(10, 10, 12, 0.44);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  opacity: 0;
  visibility: hidden;
  transition: opacity 450ms var(--ease), visibility 0s linear 450ms;
}

.drawer-backdrop.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity 450ms var(--ease), visibility 0s linear 0s;
}

.drawer {
  position: fixed;
  top: 0; right: 0; bottom: 0;
  z-index: 200;
  width: 520px;
  max-width: 100vw;
  background: var(--bg-elev);
  color: var(--ink);
  display: flex;
  flex-direction: column;
  transform: translateX(100%);
  visibility: hidden;
  transition: transform 560ms var(--ease), visibility 0s linear 560ms;
  box-shadow: -24px 0 64px rgba(0, 0, 0, 0.18);
}

.drawer.is-open {
  transform: translateX(0);
  visibility: visible;
  transition: transform 560ms var(--ease), visibility 0s linear 0s;
}

.drawer__head {
  flex: 0 0 auto;
  padding: 26px 30px;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 20px;
  border-bottom: 1px solid var(--hairline);
}

.drawer__eyebrow { display: block; margin-bottom: 8px; }

.drawer__title { font-size: 24px; font-weight: 600; letter-spacing: -0.02em; }

.drawer__close {
  flex: 0 0 auto;
  appearance: none;
  background: none;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-s);
  width: 40px; height: 40px;
  display: grid;
  place-items: center;
  cursor: pointer;
  color: var(--ink);
  transition: border-color 300ms var(--ease), background 300ms var(--ease), color 300ms var(--ease);
}
.drawer__close:hover { background: var(--ink); color: var(--bg); border-color: var(--ink); }

.drawer__body {
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 32px 30px 44px;
}

.drawer__section + .drawer__section { margin-top: 40px; }

.drawer__legend { display: flex; align-items: center; gap: 14px; margin-bottom: 20px; }
.drawer__legend::after { content: ""; flex: 1; height: 1px; background: var(--hairline); }

.drawer__meta {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 18px 22px;
  margin-top: 22px;
  padding-top: 22px;
  border-top: 1px solid var(--hairline);
}

.drawer__meta dt {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 5px;
}

.drawer__meta dd { margin: 0; font-size: 13.5px; color: var(--ink); }

/* ----------------------------------------------------------------- 16. FORMS */

.field { margin-bottom: 18px; }

.field__label {
  display: block;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 8px;
}

.input, .select, .textarea {
  width: 100%;
  background: var(--tile);
  border: 1px solid transparent;
  border-radius: var(--radius-s);
  padding: 13px 15px;
  font-family: var(--font-ui);
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink);
  transition: border-color 300ms var(--ease), background 300ms var(--ease);
  -webkit-appearance: none;
  appearance: none;
}

.input::placeholder, .textarea::placeholder { color: var(--muted); opacity: 0.7; }

.input:focus, .select:focus, .textarea:focus {
  outline: none;
  border-color: var(--ink);
  background: var(--bg);
}

.textarea { resize: vertical; min-height: 120px; }

.select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='7' viewBox='0 0 10 7'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23797C82' stroke-width='1.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 15px center;
  padding-right: 40px;
  cursor: pointer;
}

/* ------------------------------------------------------------------ PICK -- */
/* A themed dropdown, because a native <select> opens an operating-system menu
   that no stylesheet can reach — on macOS a dark panel with a system-blue
   highlight and a tick, which is the one saturated blue anywhere near this
   navy-and-sand palette.

   Progressive enhancement, not replacement: the real <select> stays in the DOM
   and stays the thing that submits. It is moved out of sight but kept
   focusable, the trigger below mirrors its value, and choosing an option writes
   back to it and dispatches `change` — so the shop's sort handler, the language
   switcher retranslating <option> text, and the no-JS case all keep working
   untouched. See initPicks() in main.js. */

.pick { position: relative; display: inline-block; max-width: 100%; }
.pick--field { display: block; width: 100%; }

/* Off-screen rather than display:none — a hidden-but-present control still
   pairs with its <label> and still reports validity. */
.pick__native {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.pick__trigger {
  appearance: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  background: none;
  border: 0;
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  text-align: left;
}

.pick__trigger svg {
  width: 10px; height: 7px;
  flex: none;
  margin-left: auto;
  transition: transform 260ms var(--ease);
}
.pick[data-open="true"] .pick__trigger svg { transform: rotate(180deg); }

/* In a form the trigger has to be indistinguishable from .input beside it. */
.pick--field .pick__trigger {
  border: 1px solid transparent;
  border-radius: var(--radius-s);
  background: var(--tile);
  padding: 13px 15px;
  font-weight: 400;
  line-height: 1.5;
  transition: border-color 300ms var(--ease), background 300ms var(--ease);
}
.formcard .pick--field .pick__trigger { background: var(--bg); }
.pick--field .pick__trigger:hover { border-color: var(--hairline-strong); }
.pick[data-open="true"] .pick--field .pick__trigger,
.pick--field[data-open="true"] .pick__trigger { border-color: var(--ink); }

.pick__native:focus-visible + .pick__trigger,
.pick__trigger:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.pick__list {
  position: absolute;
  z-index: 40;
  top: calc(100% + 6px);
  left: 0;
  min-width: 100%;
  max-height: 280px;
  overflow-y: auto;
  margin: 0;
  padding: 6px;
  list-style: none;
  background: var(--bg);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-s);
  box-shadow: 0 2px 6px rgba(27, 42, 65, 0.06), 0 18px 40px rgba(27, 42, 65, 0.14);
}
.pick__list[hidden] { display: none; }

/* Right-aligned where the trigger sits at the right edge of its row, so the
   panel does not push the page sideways. */
.pick--end .pick__list { left: auto; right: 0; }

.pick__opt {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 11px;
  border-radius: calc(var(--radius-s) - 3px);
  font-size: 14px;
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;
}

/* One highlight, driven by keyboard and pointer alike, so arrowing down and
   hovering cannot disagree about what is current. */
.pick__opt[data-active="true"] { background: var(--tile-strong); }

.pick__opt::before {
  content: "";
  width: 14px; height: 14px;
  flex: none;
  border-radius: 50%;
  border: 1px solid var(--hairline-strong);
  transition: background 200ms var(--ease), border-color 200ms var(--ease);
}
.pick__opt[aria-selected="true"]::before {
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: inset 0 0 0 3px var(--bg);
}
.pick__opt[aria-selected="true"] { font-weight: 600; }

@media (prefers-reduced-motion: reduce) {
  .pick__trigger svg, .pick__opt::before { transition: none; }
}

/* ------------------------------------------------------------ FIELD ERROR -- */
/* Replaces the browser's validation bubble — another OS-drawn panel, and one
   that vanishes on the next click and is never read by anyone using a
   screen reader on some platforms. This is in the page, tied to the input by
   aria-describedby, and survives until the field is fixed. */

.field__error {
  display: none;
  margin: 7px 0 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: #B4443A;
}
.field__error.is-visible { display: block; }

[data-theme="dark"] .field__error { color: #E4877A; }

.input[aria-invalid="true"],
.textarea[aria-invalid="true"],
.pick[aria-invalid="true"] .pick__trigger {
  border-color: #B4443A;
}

.field-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0 16px; }

.form__note { margin-top: 14px; font-size: 11.5px; line-height: 1.6; color: var(--muted); }

.form__status {
  margin-top: 14px;
  padding: 13px 15px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-s);
  background: var(--tile);
  font-size: 13px;
  color: var(--ink);
  display: none;
}
.form__status.is-visible { display: block; }
.form__status.is-ok { border-color: var(--accent); background: var(--accent-soft); }
.form__status.is-error { border-color: #B4443A; background: rgba(180, 68, 58, 0.08); }

.btn.is-busy { opacity: 0.6; cursor: progress; }

/* ----------------------------------------------- 17. EDITORIAL / INSIGHTS */

.feature {
  display: grid;
  grid-template-columns: 58% 42%;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg-elev);
}

.feature__media { position: relative; overflow: hidden; background: var(--tile); min-height: 340px; }
.feature__media svg, .feature__media img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}

.feature__body {
  padding: clamp(28px, 3.4vw, 52px);
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.feature__tag { display: block; margin-bottom: 18px; }
.feature__title { font-size: clamp(24px, 2.4vw, 34px); font-weight: 600; letter-spacing: -0.02em; margin-bottom: 14px; max-width: 24ch; }
.feature__date { font-size: 12.5px; color: var(--muted); margin-bottom: 16px; font-variant-numeric: tabular-nums; }
.feature__excerpt { font-size: 14.5px; line-height: 1.7; color: var(--muted); margin-bottom: 26px; max-width: 48ch; }

.grid-notes { display: grid; grid-template-columns: repeat(2, 1fr); gap: clamp(16px, 1.6vw, 26px); }

.note {
  padding: clamp(24px, 2.4vw, 34px);
  background: var(--tile);
  border-radius: var(--radius);
  border: 1px solid transparent;
  display: flex;
  flex-direction: column;
  transition: background 400ms var(--ease), border-color 400ms var(--ease);
}

.note:hover { border-color: var(--hairline); background: var(--tile-strong); }

.note__tag { display: block; margin-bottom: 14px; }
.note__title { font-size: 19px; font-weight: 600; letter-spacing: -0.015em; margin-bottom: 8px; max-width: 26ch; }
.note__date { font-size: 12.5px; color: var(--muted); margin-bottom: 12px; font-variant-numeric: tabular-nums; }
.note__excerpt { font-size: 14px; line-height: 1.65; color: var(--muted); margin-bottom: 22px; max-width: 46ch; }
.note__link { margin-top: auto; align-self: flex-start; }

/* ------------------------------------------------------------ 17b. ARTICLE */

.article { padding: calc(var(--nav-h) + clamp(40px, 5vw, 72px)) 0 clamp(64px, 7vw, 104px); }

.article .shell { max-width: 820px; }

.article__back { display: inline-flex; margin-bottom: 32px; }

.article__tag { display: block; margin-bottom: 16px; }

.article__title {
  font-size: clamp(28px, 3.4vw, 46px);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.1;
  margin-bottom: 18px;
}

.article__meta {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 13px;
  color: var(--muted);
  margin-bottom: 40px;
}

.article__dot { width: 3px; height: 3px; border-radius: 50%; background: var(--accent); }

.article__figure {
  margin: 0 0 clamp(36px, 4vw, 56px);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--tile);
}
.article__figure img { width: 100%; height: auto; display: block; }

.article__body { font-size: 16.5px; line-height: 1.75; color: var(--ink-soft); }

.article__h2 {
  font-size: clamp(20px, 2vw, 26px);
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--ink);
  margin: clamp(32px, 3.4vw, 48px) 0 14px;
}

.article__p { margin-bottom: 18px; max-width: 68ch; }

.article__ul {
  margin: 0 0 22px;
  padding: 0;
  list-style: none;
  max-width: 68ch;
}
.article__ul li {
  position: relative;
  padding-left: 22px;
  margin-bottom: 9px;
}
.article__ul li::before {
  content: "";
  position: absolute;
  left: 2px; top: 11px;
  width: 6px; height: 1px;
  background: var(--accent);
}

.article__cta {
  margin-top: clamp(48px, 5vw, 72px);
  padding: clamp(28px, 3vw, 44px);
  background: var(--tile);
  border-radius: var(--radius);
  text-align: center;
}
.article__cta .body-copy { max-width: 52ch; margin: 12px auto 24px; }

/* ------------------------------------------------------------- 18. CONTACT */

.contact-grid {
  display: grid;
  grid-template-columns: 9fr 11fr;  /* not 45%/55% — percentages ignore the gap */
  gap: 0 clamp(32px, 5vw, 80px);
  align-items: start;
}

.desk__dl { border-top: 1px solid var(--hairline); }

.desk__dl > div {
  display: grid;
  grid-template-columns: 140px 1fr;
  gap: 18px;
  padding: 18px 0;
  border-bottom: 1px solid var(--hairline);
}

.desk__dl dt {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  padding-top: 3px;
}

.desk__dl dd { margin: 0; font-size: 14px; line-height: 1.6; color: var(--ink); }
.desk__dl dd a { border-bottom: 1px solid var(--hairline); padding-bottom: 1px; }
.desk__dl dd a:hover { border-color: var(--ink); }

.desk__legal { font-size: 15px; font-weight: 600; line-height: 1.45; letter-spacing: 0; }

.desk__cta { margin-top: 36px; display: flex; flex-wrap: wrap; gap: 14px; }

.formcard {
  background: var(--tile);
  padding: clamp(28px, 3vw, 48px);
  border-radius: var(--radius);
}
.formcard .input, .formcard .select, .formcard .textarea { background: var(--bg); }
.formcard .input:focus, .formcard .select:focus, .formcard .textarea:focus { background: var(--bg); }
.formcard__title { margin-bottom: 10px; }
.formcard__copy { font-size: 14px; color: var(--muted); margin-bottom: 28px; max-width: 46ch; }

/* --------------------------------------------------------- SOURCING DESK */
/* The two things a visitor can be, stated before the form asks. */

.sourcing__intro { max-width: 62ch; margin-bottom: clamp(36px, 4vw, 56px); }
.sourcing__title { margin: 14px 0 18px; }
.sourcing__copy { font-size: clamp(15px, 1.15vw, 17px); line-height: 1.7; color: var(--muted); }

.sourcing__paths {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(18px, 2vw, 28px);
}

.path {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  padding: clamp(26px, 2.6vw, 40px);
  border-radius: var(--radius);
  background: var(--tile);
  transition: background 400ms var(--ease);
}

.path:hover { background: var(--tile-strong); }

.path__n {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.14em;
  color: var(--accent);
  margin-bottom: 16px;
}

.path__title { margin-bottom: 12px; }

/* Pushes the button to a common baseline whichever card has the longer copy. */
.path__copy { font-size: 14px; line-height: 1.7; color: var(--muted); margin-bottom: 26px; flex: 1 1 auto; }

/* ---------------------------------------------------------- ENQUIRY DESK */
/* Segmented switcher over one form. The card sits on --tile, so the group
   takes --bg as its trough and the active tab inverts to the ink. */

.enq__tabs {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 4px;
  padding: 4px;
  margin-bottom: 30px;
  border-radius: var(--radius-s);
  background: var(--bg);
}

.enq__tab {
  appearance: none;
  border: 0;
  cursor: pointer;
  padding: 11px 10px;
  border-radius: calc(var(--radius-s) - 2px);
  background: none;
  color: var(--muted);
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-align: center;
  white-space: nowrap;
  transition: background 300ms var(--ease), color 300ms var(--ease);
}

.enq__tab:hover { color: var(--ink); }

.enq__tab.is-active {
  background: var(--ink);
  color: var(--bg);
}

/* [hidden] alone is enough to switch panels — the fade is only so the change
   registers as a change rather than a redraw. */
.enq__panel[hidden] { display: none; }

@media (prefers-reduced-motion: no-preference) {
  .enq__panel:not([hidden]) { animation: enqIn 320ms var(--ease) both; }
}

@keyframes enqIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* ------------------------------------------------------------ FILE FIELD */
/* The native control cannot be styled and states the filename in a system
   font. The input stays in the DOM and keeps doing the work; the label is
   what you see and click. */

.filefield { position: relative; }

.filefield__input {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.filefield__face {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 14px 16px;
  border: 1px dashed var(--hairline-strong);
  border-radius: var(--radius-s);
  background: var(--bg);
  cursor: pointer;
  font-size: 13.5px;
  color: var(--muted);
  transition: border-color 300ms var(--ease), color 300ms var(--ease);
}

.filefield__face:hover { border-color: var(--ink); color: var(--ink); }

/* Keyboard focus lands on the visually-hidden input, so the ring has to be
   drawn on the label that stands in for it. */
.filefield__input:focus-visible + .filefield__face {
  border-color: var(--ink);
  border-style: solid;
  color: var(--ink);
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.filefield__face svg { width: 17px; height: 17px; flex: 0 0 auto; }
.filefield__label { font-weight: 600; letter-spacing: 0.02em; }

.filefield__name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink);
}

.filefield.has-file .filefield__face { border-style: solid; border-color: var(--accent); color: var(--ink); }
.filefield.has-file .filefield__label { display: none; }

.filefield.is-invalid .filefield__face { border-style: solid; border-color: #B4443A; color: #B4443A; }

.filefield__note { margin: 8px 0 0; font-size: 11px; letter-spacing: 0.04em; color: var(--muted); }

/* Map */
.mapframe {
  position: relative;
  width: 100%;
  height: 360px;
  margin-top: clamp(48px, 6vw, 80px);
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--tile);
}

.mapframe iframe {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  border: 0;
  filter: grayscale(1) contrast(1.04);
}

[data-theme="dark"] .mapframe iframe {
  filter: grayscale(1) invert(0.92) hue-rotate(180deg) contrast(0.94) brightness(0.9);
}

.mapframe__tint { display: none; }

.mapframe__plate {
  position: absolute;
  left: 14px; bottom: 14px;
  z-index: 2;
  background: var(--bg);
  color: var(--ink);
  border-radius: var(--radius-s);
  padding: 14px 20px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
}
.mapframe__plate strong { display: block; font-size: 15px; font-weight: 600; margin-bottom: 2px; }
.mapframe__plate span {
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

/* -------------------------------------------------- 19. LEGACY HOME SECTIONS */

/* Qualification — compact editorial strip */
/* ---- Client qualification (home) ------------------------------------------
   A self-qualifier, not a list. See the markup note in build_pages.py for why.
   Everything below serves one idea: the row should feel selectable before it
   is clicked, and settled after.                                            */

.qual__head { max-width: 40ch; margin-bottom: clamp(28px, 3vw, 40px); }
.qual__title { text-wrap: balance; }
.qual__lead {
  margin-top: 14px;
  font-size: 15px;
  line-height: 1.65;
  color: var(--muted);
  max-width: 46ch;
}

.qual__list { border-top: 1px solid var(--hairline); }

.qual__item { position: relative; border-bottom: 1px solid var(--hairline-soft); }

/* The input is the state and the focus target; the label is the whole row, so
   anywhere on a criterion is a hit area. Hidden with clip rather than
   display:none — display:none removes it from the tab order. */
.qual__box {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.qual__label {
  display: grid;
  grid-template-columns: 26px var(--idx-gutter) 1fr;
  align-items: baseline;
  gap: 0 14px;
  padding: clamp(17px, 2vw, 26px) 0;
  cursor: pointer;
  transition: background 320ms var(--ease);
}

/* A square that becomes a mark. Sits on the text baseline rather than centred
   in the row, so it reads as part of the sentence. */
.qual__tick {
  position: relative;
  top: 0.18em;
  width: 20px; height: 20px;
  border: 1.5px solid var(--hairline);
  border-radius: 3px;
  color: transparent;
  display: grid;
  place-items: center;
  transition: border-color 260ms var(--ease), background 260ms var(--ease),
              color 260ms var(--ease);
}
.qual__tick svg { width: 13px; height: 13px; }

.qual__num {
  font-family: var(--font-mono);
  font-size: var(--idx-size);
  font-weight: 500;
  letter-spacing: 0.06em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  transition: color 260ms var(--ease);
}

.qual__text {
  font-size: clamp(16px, 1.55vw, 20px);
  font-weight: 500;
  line-height: 1.45;
  letter-spacing: -0.012em;
  color: var(--ink-soft);
  max-width: 48ch;
  transition: color 260ms var(--ease);
}

@media (hover: hover) and (pointer: fine) {
  .qual__label:hover { background: var(--accent-soft); }
  .qual__label:hover .qual__tick { border-color: var(--accent); }
}

/* Checked. The row commits: gold mark, full-strength text, gold numeral. */
.qual__box:checked + .qual__label .qual__tick {
  background: var(--accent);
  border-color: var(--accent);
  color: #FFFFFF;
}
.qual__box:checked + .qual__label .qual__text { color: var(--ink); }
.qual__box:checked + .qual__label .qual__num { color: var(--accent); }

/* Keyboard users get the ring on the row, since the input itself is clipped. */
.qual__box:focus-visible + .qual__label {
  outline: 1.5px solid var(--focus);
  outline-offset: 2px;
  border-radius: 3px;
}

/* ---- the answer ---- */

.qual__result { margin-top: clamp(26px, 3vw, 40px); }

.qual__meter {
  height: 2px;
  background: var(--hairline-soft);
  overflow: hidden;
}
.qual__meter i {
  display: block;
  height: 100%;
  width: 0;
  background: var(--accent);
  transition: width 520ms var(--ease);
}

.qual__answer {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 18px 40px;
  padding-top: clamp(18px, 2vw, 26px);
}

.qual__verdict {
  display: flex;
  align-items: baseline;
  gap: 2px;
  font-size: clamp(16px, 1.7vw, 21px);
  font-weight: 600;
  letter-spacing: -0.014em;
  line-height: 1.4;
  max-width: 46ch;
}

.qual__count { color: var(--accent); font-variant-numeric: tabular-nums; }
.qual__of { color: var(--muted); font-weight: 500; margin-right: 12px; }

/* Three mutually exclusive readings of the same count. JS only moves
   data-state, so the wording stays with the language file. */
.qual__say { display: none; font-weight: 600; }
.qual__result[data-state="0"] .qual__say--none { display: inline; color: var(--muted); }
.qual__result[data-state="some"] .qual__say--some { display: inline; }
.qual__result[data-state="all"] .qual__say--all { display: inline; }

.qual__act { display: flex; align-items: center; gap: 22px; flex-wrap: wrap; }

/* The request is the point of the section, but it has not earned attention
   until something is marked — so it arrives rather than sitting there.

   Gated on .is-live, which initQualify adds. Written the other way round
   first, and that hid the button permanently when JS was off: data-state never
   changed, so opacity:0 and pointer-events:none were the final state and the
   one real call to action on the section was unreachable. The enhancement has
   to be the thing that opts in, never the fallback. */
.qual__result.is-live .qual__cta {
  opacity: 0;
  transform: translateY(6px);
  pointer-events: none;
  transition: opacity 460ms var(--ease), transform 460ms var(--ease);
}
.qual__result.is-live[data-state="some"] .qual__cta,
.qual__result.is-live[data-state="all"] .qual__cta {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.qual__more {
  position: relative;
  flex: none;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent-hover);
}
.qual__more::after {
  content: "";
  position: absolute;
  left: 0; bottom: -3px;
  width: 100%; height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 380ms var(--ease);
}
.qual__more:hover::after,
.qual__more:focus-visible::after { transform: scaleX(1); }

@media (max-width: 700px) {
  .qual__label { grid-template-columns: 24px 1fr; gap: 0 12px; }
  .qual__num { display: none; }          /* the tick is the marker here */
  .qual__answer { display: block; }
  .qual__act { margin-top: 20px; }
}

@media (prefers-reduced-motion: reduce) {
  .qual__meter i, .qual__cta, .qual__tick, .qual__label { transition: none; }
}

/* Guarantees ribbon */
/* --------------------------------------------------------------- PROCESS --
   The six-step sequence that replaced the guarantees ribbon. Its three claims
   are steps 04, 05 and 06 here.

   Set as a manifest, like the corridor waypoints, the qualification ledger and
   the footer columns — same numeral, same hairline. The one addition is that a
   step's rule is gold, because these are ordered: the numeral is load-bearing
   information here rather than decoration.                                  */

.flow__head { max-width: 46ch; margin-bottom: clamp(36px, 4vw, 58px); }
.flow__title { text-wrap: balance; }
.flow__copy { margin-top: 16px; max-width: 52ch; }

.flow__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(28px, 3vw, 48px) clamp(28px, 4vw, 64px);
}

.flow__step {
  padding-top: 18px;
  border-top: 1.5px solid var(--accent);
}

/* Steps 04-06 sit on a hairline instead, so the eye reads the grid as two
   halves — commit, then execute — rather than six equal things. */
.flow__step:nth-child(n+4) { border-top-color: var(--hairline); }

.flow__num {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--idx-size);
  font-weight: 500;
  letter-spacing: 0.06em;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  margin-bottom: 12px;
}

.flow__stepTitle {
  font-size: clamp(17px, 1.5vw, 21px);
  font-weight: 600;
  letter-spacing: -0.015em;
  margin-bottom: 9px;
}

.flow__stepCopy {
  font-size: 14.5px;
  line-height: 1.65;
  color: var(--muted);
  max-width: 42ch;
}

.flow__seo {
  margin: clamp(38px, 4vw, 60px) 0 0;
  max-width: 78ch;
  font-size: 13px;
  line-height: 1.7;
  color: var(--muted);
}

@media (max-width: 1000px) {
  .flow__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* The two-half reading only works in threes; on two columns every step gets
     the same rule rather than a split that now lands mid-row. */
  .flow__step:nth-child(n+4) { border-top-color: var(--accent); }
}

@media (max-width: 600px) {
  .flow__grid { grid-template-columns: 1fr; gap: 26px; }
}

/* Trade corridor — monochrome restyle */
.corridor__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 40px;
  flex-wrap: wrap;
  margin-bottom: clamp(40px, 4vw, 64px);
}


/* ---- Globe -----------------------------------------------------------------
   The lanes drawn on the actual planet. It only ever replaces the flat figure
   once WebGL has drawn something, so the SVG below stays the fallback for
   phones, missing WebGL, reduced motion and no-JS alike.                     */

.corridor__grid {
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  gap: clamp(32px, 4vw, 60px);
  align-items: center;
}

.corridor__globe {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  max-height: 70vh;
  border-radius: var(--radius);
  overflow: hidden;
  /* No card behind it. This site frames things with crop marks and hairlines,
     not panels, and a gradient plate read as a floating dark box in the matte
     theme. The globe sits on the page ground like every other figure. */
  background: transparent;
  cursor: grab;
  touch-action: pan-y;          /* the page keeps vertical scroll on touch */
  opacity: 0;
  transition: opacity 900ms var(--ease);
}
.corridor__globe.is-live { opacity: 1; }
.corridor__globe:active { cursor: grabbing; }
.corridor__globe canvas { display: block; width: 100%; height: 100%; }

/* Labels are HTML projected each frame, so the type stays Manrope rather than
   being baked into a texture. */
.globe__lbl {
  position: absolute;
  transform: translate(-50%, -50%);
  pointer-events: none;
  white-space: nowrap;
  opacity: 0;
  transition: opacity 220ms var(--ease);
}
.globe__lbl b {
  font-family: var(--font-ui);
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink);
  text-shadow: 0 0 5px var(--tile), 0 0 5px var(--tile), 0 0 5px var(--tile);
}

.corridor__hint {
  margin-top: 14px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}

/* The waypoints are a manifest, and the site now has one way of setting a
   manifest — see --idx-* in section 1. Same numeral column, same row height,
   same hairline as the qualification list and the footer columns, so three
   sections that were each inventing their own list read as one document.
   The numerals are generated rather than marked up: they are the row's index,
   not content, and the RU translation must not have to carry them. */
/* Starts at -1 so the head row absorbs the first increment and Black Sea
   reads 01. */
.corridor__lanes { width: 100%; border-collapse: collapse; counter-reset: lane -1; }
.corridor__lanes tr { counter-increment: lane; }

.corridor__lanes th, .corridor__lanes td {
  text-align: left;
  padding: var(--idx-row-y) 10px var(--idx-row-y) 0;
  border-bottom: 1px solid var(--hairline-soft);
}
.corridor__lanes tr:last-child td { border-bottom: 0; }

/* Column heads read as the manifest's own label row, not as table furniture. */
.corridor__lanes th {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  padding-bottom: 10px;
  border-bottom-color: var(--hairline);
}

.corridor__lanes td { font-size: 14.5px; color: var(--ink); }

/* Waypoint cell carries the index in its own fixed gutter, so the names align
   with each other and not with the digits. */
.corridor__lanes tr:not(:first-child) td:first-child {
  position: relative;
  padding-left: var(--idx-gutter);
  font-weight: 600;
}
.corridor__lanes tr:not(:first-child) td:first-child::before {
  content: counter(lane, decimal-leading-zero);
  position: absolute;
  left: 0;
  font-family: var(--font-mono);
  font-size: var(--idx-size);
  font-weight: 500;
  letter-spacing: 0.06em;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}
/* The head row is counted too — offset so Black Sea reads 01, not 02. */
.corridor__lanes { counter-reset: lane -1; }

.corridor__lanes td.role {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  color: var(--muted);
  text-transform: uppercase;
}

/* Unlit is the resting state: the manifest is legible but recessive, and each
   row comes up as its lane finishes drawing on the globe. Scroll drives both
   from one number (initCorridorReveal), so a row can never light for a lane
   that has not arrived. Colour and weight only — nothing here moves, because
   five rows sliding in beside a rotating globe is two animations competing. */
.corridor__lanes tr:not(:first-child) td {
  color: var(--muted);
  transition: color 500ms var(--ease);
}
.corridor__lanes tr:not(:first-child) td:first-child::before {
  opacity: 0.3;
  transition: opacity 500ms var(--ease);
}
.corridor__lanes tr.is-lit td { color: var(--ink); }
.corridor__lanes tr.is-lit td.role { color: var(--accent); }
.corridor__lanes tr.is-lit td:first-child::before { opacity: 1; }

/* A hairline that fills left-to-right as the row lights, so the manifest reads
   as a route being walked rather than a list being ticked. */
.corridor__lanes tr:not(:first-child) td:first-child::after {
  content: "";
  position: absolute;
  left: 0; bottom: -1px;
  width: 100%; height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 700ms var(--ease);
}
.corridor__lanes tr.is-lit td:first-child::after { transform: scaleX(1); }

/* Picking a waypoint. The rows are the keyboard path into the globe, so they
   have to look and behave like controls: a pointer, a focus ring, and a picked
   state that is louder than "connected" without introducing a second colour. */
.corridor__lanes tr:not(:first-child) { cursor: pointer; }
.corridor__lanes tr:not(:first-child):hover td { color: var(--ink); }
.corridor__lanes tr:not(:first-child):focus-visible {
  outline: 1.5px solid var(--focus);
  outline-offset: 2px;
}

.corridor__lanes tr.is-picked td { color: var(--ink); }
.corridor__lanes tr.is-picked td.role { color: var(--accent); }
.corridor__lanes tr.is-picked td:first-child::before { opacity: 1; color: var(--accent); }
.corridor__lanes tr.is-picked td:first-child::after { transform: scaleX(1); height: 2px; }

/* The unpicked rows recede so the picked leg is the only thing being read —
   the same move the sphere makes with its lane opacity. */
.corridor__lanes:has(tr.is-picked) tr:not(.is-picked):not(:first-child) td {
  color: var(--muted);
  transition: color 320ms var(--ease);
}

/* No scroll choreography to hook into, so the finished state is the state. */
@media (prefers-reduced-motion: reduce) {
  .corridor__lanes tr:not(:first-child) td { color: var(--ink); }
  .corridor__lanes tr:not(:first-child) td:first-child::before { opacity: 1; }
  .corridor__lanes tr:not(:first-child) td:first-child::after { transform: scaleX(1); }
}

/* Below this the globe is never mounted, so the grid collapses to the SVG. */
/* ------------------------------------------------- corridor on a phone --- */
/* The globe and the manifest used to be hidden below 1000px, which left phones
   on the flat route diagram — a different design from the one the section
   actually is now. Both are present at every width; the sphere just gets
   smaller and the manifest restacks.

   Everything here is guarded on :not([hidden]) because the markup ships the
   globe and table hidden and main.js only reveals them once WebGL has actually
   mounted. An unguarded `display` would beat the hidden attribute and show an
   empty table on the devices that fall back to the flat SVG. */
@media (max-width: 999px) {
  .corridor__grid { grid-template-columns: 1fr; gap: 30px; }

  /* Centre the whole column rather than relying on the globe's own auto
     margins, so the hint sits under the middle of the sphere too. */
  .corridor__grid > div:first-child {
    display: flex;
    flex-direction: column;
    align-items: center;
  }

  /* Sized off the viewport, not the column: a square that fills the measure
     eats a whole phone screen. Capped so a tablet does not get a giant one. */
  .corridor__globe {
    width: min(76vw, 380px);
    margin: 0 auto;
    max-height: none;
  }
  .corridor__hint { text-align: center; }

  /* The manifest restacks: name and role on one line, port beneath, aligned to
     the name rather than to the numeral. Three columns of trade data do not
     fit 390px, and shrinking the type until they do makes it unreadable. */
  .corridor__lanes:not([hidden]) { display: block; }
  .corridor__lanes tbody { display: block; }
  .corridor__lanes tr:first-child { display: none; }   /* the column heads */
  /* display:none stops an element incrementing a counter, so with the head row
     gone the offset that made Black Sea read 01 instead shifted it to 00. */
  .corridor__lanes { counter-reset: lane 0; }

  .corridor__lanes tr:not(:first-child) {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: baseline;
    gap: 3px 14px;
    padding: 13px 0;
    border-bottom: 1px solid var(--hairline-soft);
    transition: border-color 600ms var(--ease);
  }
  .corridor__lanes tr:not(:first-child) td { display: block; padding: 0; border-bottom: 0; }
  .corridor__lanes tr:not(:first-child) td:first-child { grid-area: 1 / 1; font-size: 16px; }
  .corridor__lanes td.role { grid-area: 1 / 2; text-align: right; font-size: 10px; }
  .corridor__lanes tr:not(:first-child) td:nth-child(2) {
    grid-area: 2 / 1 / 2 / 3;
    padding-left: var(--idx-gutter);
    font-size: 13px;
    color: var(--muted);
  }

  /* The left-to-right fill rule lived on the name cell, so restacked it would
     only ever underline half a row. The whole row's rule carries the state
     instead — a colour change rather than a wipe, which is the one thing that
     survives a layout this narrow. */
  .corridor__lanes tr:not(:first-child) td:first-child::after { display: none; }
  .corridor__lanes tr.is-lit { border-bottom-color: var(--accent); }
  .corridor__lanes tr.is-picked { border-bottom-color: var(--accent); border-bottom-width: 2px; }

  /* Rows are tap targets here, so they need the height for it. */
  .corridor__lanes tr:not(:first-child) { min-height: 54px; align-content: center; }
  .corridor__lanes tr:not(:first-child):hover td { color: inherit; }  /* no hover on touch */
}

/* One hint per input model — "drag to turn" is a lie on a phone, where a drag
   scrolls the page. */
.corridor__hint--coarse { display: none; }
@media (pointer: coarse) {
  .corridor__hint--fine { display: none; }
  .corridor__hint--coarse { display: inline; }
}


.corridor.in-view .corridor__pulse { animation: nodePulse 3.2s var(--ease) infinite; }

@keyframes nodePulse {
  0%   { transform: scale(1);   opacity: 0.4; }
  70%  { transform: scale(3.4); opacity: 0; }
  100% { transform: scale(3.4); opacity: 0; }
}


/* The caravan riding the route. animateMotion places it, so the only job
   here is colour and a size that stays legible at both orientations. */

@media (prefers-reduced-motion: reduce) {
}


/* Closing CTA band. Two columns on one hairline: the ask, and the terms a
   counterparty checks before writing. Left-aligned like everything above it. */
.closing__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  gap: clamp(40px, 6vw, 100px);
  align-items: start;
}

.closing__title { max-width: 20ch; margin-bottom: 18px; text-wrap: balance; }
.closing__copy { max-width: 46ch; margin-bottom: 36px; color: var(--muted); font-size: 15.5px; }
.closing__actions { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }

/* The terms read as a register: mono keys, hairline per row, values in ink.
   Same rhythm as the corridor manifest, one column narrower. */
.closing__terms { margin: 0; border-top: 1px solid var(--hairline); }
.closing__terms > div {
  display: grid;
  grid-template-columns: minmax(0, 0.62fr) minmax(0, 1fr);
  gap: 12px 24px;
  padding: var(--idx-row-y) 0;
  border-bottom: 1px solid var(--hairline-soft);
}
.closing__terms dt {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  padding-top: 2px;
}
.closing__terms dd {
  margin: 0;
  font-size: 14.5px;
  line-height: 1.5;
  color: var(--ink);
}
/* The quotation clause is a condition of trade, not a value — set quieter so
   the three facts above it stay scannable. */
.closing__terms > div:last-child dd { font-size: 13px; color: var(--muted); }

@media (max-width: 860px) {
  .sourcing__paths { grid-template-columns: 1fr; }
  .closing__grid { grid-template-columns: 1fr; gap: 48px; }
}

/* ----------------------------------------------------------------- 20. FOOTER */
/* The closing plate of a trading dossier: a live desk readout, numbered
   manifest columns, the wordmark struck across the base, and a registry strip.
   Every colour resolves through footer-scoped --f-* tokens. That indirection
   still earns its place now the band follows the theme: the gold needs a
   different value on ivory than on the dark ground, and one variable is a
   better place for that than forty call sites. */

/* The band used to be --inverse-bg, which meant it flipped AGAINST the theme:
   navy under the light site, ivory under the dark one. So on the dark site the
   page ended in a bright slab, and the two themes each had a footer belonging
   to the other. It reads as a different site bolted to the bottom, and it is
   the single biggest reason the pages did not feel like one document.

   It now sits on the page ground and follows the theme, separated by the
   dashed rule rather than by a colour change — which is what every other
   surface here already does ("surfaces only a shade apart", section 1. */
.footer {
  --f-accent:  var(--accent-hover);          /* 4.1:1 on ivory; plain --accent is 2.1:1 */
  --f-ink:     var(--ink);
  --f-muted:   var(--muted);
  --f-line:    var(--hairline);
  --f-line-s:  var(--hairline-soft);
  --f-plate:   var(--hairline-soft);

  position: relative;
  background: var(--bg);
  color: var(--f-ink);
  padding: clamp(56px, 6vw, 96px) 0 32px;
  transition: background 400ms var(--ease), color 400ms var(--ease);
  overflow: hidden;                          /* the wordmark plate bleeds */
}

/* Dark ground takes the lifted gold — --accent-hover goes the wrong way there,
   darker against an already dark field. */
[data-theme="dark"] .footer { --f-accent: var(--accent); }

/* Top edge: the corridor route line from section 19, dashed and faded at both
   ends so the band arrives rather than starts. */
.footer__rule {
  height: 2px;
  background: repeating-linear-gradient(90deg,
              var(--accent) 0 15px, transparent 15px 27px);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}

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

.footer__top {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 40px;
  flex-wrap: wrap;
  padding-bottom: clamp(34px, 3.5vw, 52px);
  border-bottom: 1px solid var(--f-line);
}

.footer__claim {
  font-size: clamp(21px, 2.5vw, 31px);
  font-weight: 600;
  line-height: 1.22;
  letter-spacing: -0.022em;
  max-width: 26ch;      /* holds "Delivered across continents." on one line */
}
.footer__claim em { font-style: normal; display: block; color: var(--f-accent); }

/* Live desk readout — the hero's amber status dot, carried to the base of
   every page. Falls back to plain opening hours when JS never runs. */
.footer__desk { text-align: right; }

.footer__state {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 9px;
  font-size: 13px;
  color: var(--f-ink);
}

.footer__dot {
  width: 7px;
  height: 7px;
  flex: none;
  border-radius: 50%;
  background: var(--f-muted);
  box-shadow: 0 0 0 0 transparent;
}
.footer__desk[data-open="1"] .footer__dot {
  background: var(--accent);
  animation: deskPulse 2.6s var(--ease) infinite;
}
.footer__desk[data-open="0"] .footer__dot { background: var(--f-muted); opacity: 0.55; }

@keyframes deskPulse {
  0%, 100% { box-shadow: 0 0 0 0 var(--accent-soft); }
  50%      { box-shadow: 0 0 0 6px transparent; }
}

/* Three mutually exclusive states; JS only ever swaps the data attribute, so
   the language switcher keeps ownership of the text. */
.footer__st { display: none; }
.footer__desk:not([data-open]) .footer__st--hours { display: inline; }
.footer__desk[data-open="1"] .footer__st--open   { display: inline; }
.footer__desk[data-open="0"] .footer__st--shut   { display: inline; }

.footer__clock {
  margin-top: 7px;
  display: flex;
  align-items: baseline;
  justify-content: flex-end;
  gap: 10px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--f-muted);
}
.footer__clock .tabular { font-size: 13px; letter-spacing: 0.04em; color: var(--f-accent); }
/* Until JS reports a time there is nothing to show, so the placeholder digits
   stay hidden rather than reading as a broken clock. */
.footer__desk:not([data-open]) .footer__clock .tabular { display: none; }

/* -------------------------------------------------------- manifest cols --- */

.footer__cols {
  display: grid;
  /* Two columns since the footer was cut from four: where to go, and how to
     reach the desk. Weighted so the desk column keeps its breathing room. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: 44px clamp(40px, 8vw, 120px);
  padding: clamp(38px, 4vw, 58px) 0 clamp(20px, 2vw, 30px);
}

/* Third call site of the manifest spec. The numeral takes the same fixed
   gutter as the corridor waypoints and the qualification rows, so the column
   heads start on the same vertical as those lists rather than 10px off it. */
.footer__h {
  display: grid;
  grid-template-columns: var(--idx-gutter) 1fr;
  align-items: baseline;
  margin-bottom: 20px;
  padding-bottom: 11px;
  border-bottom: 1px solid var(--f-line-s);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  color: var(--f-ink);
}
.footer__h i {
  font-family: var(--font-mono);
  font-style: normal;
  font-size: var(--idx-size);
  font-weight: 500;
  letter-spacing: 0.06em;
  color: var(--f-accent);
}

/* Everything under a head aligns with that head's label, not with its numeral.
   The numeral column stays empty below the first row, which is what makes the
   gutter read as a margin rather than as an indent. */
.footer__col > *:not(.footer__h) { padding-left: var(--idx-gutter); }

.footer__list li + li { margin-top: 11px; }

.footer__list a {
  position: relative;
  display: inline-block;
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--f-muted);
  transition: color 280ms var(--ease);
}
/* A rule that draws itself in from the left — the corridor stroke again, at
   link scale. */
.footer__list a::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -2px;
  width: 100%;
  height: 1px;
  background: var(--f-accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 380ms var(--ease);
}
.footer__list a:hover,
.footer__list a:focus-visible { color: var(--f-ink); }
.footer__list a:hover::after,
.footer__list a:focus-visible::after { transform: scaleX(1); }

/* Written at the same weight as .footer__list a so it overrides without
   !important. */
.footer__list a.footer__more {
  margin-top: 4px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--f-accent);
}

/* Desk column — same specificity note: the base rule sets inline-block, which
   would drop each icon onto its own line. */
.footer__list--desk li + li { margin-top: 14px; }
.footer__list a.footer__line { display: flex; align-items: center; gap: 10px; }
.footer__line svg { width: 15px; height: 15px; flex: none; color: var(--f-accent); }

.footer__addr {
  margin-top: 20px;
  font-style: normal;
  font-size: 13px;
  line-height: 1.7;
  color: var(--f-muted);
  max-width: 26ch;
}

.footer__reply {
  margin-top: 14px;
  padding-top: 13px;
  border-top: 1px solid var(--f-line-s);
  font-family: var(--font-mono);
  font-size: 10px;
  line-height: 1.6;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--f-muted);
}

/* ------------------------------------------------------- wordmark plate --- */
/* Struck across the base at display scale, outlined rather than filled so it
   reads as a watermark on the document instead of a second headline. */

/* The sign-off, in two parts. The lockup is the real signature and the only
   link — the plate below it used to be a second link to the same page, which
   is a duplicate stop for anyone tabbing through. */
/* The sign-off is the full lockup — mark, wordmark and descriptor — and it is
   sized by the descriptor rather than by taste: that line is 12/128 of the
   mark, so a mark under about 100px puts it below 9px and the third line stops
   being readable. This is the one place on the site with room for it, which is
   exactly why the full logo belongs here and the reduced one belongs in the
   bar. */
.footer__sign.lockup {
  --lockup: clamp(74px, 8.4vw, 108px);
  /* Two classes deliberately: this has to outrank [data-theme="dark"] .lockup
     so the footer keeps its own tokens in both themes. */
  --mark-sand: var(--f-accent);
  --mark-lit: #D9BA83;
  /* A hairline ring vanishes at 108px; it scales with the mark instead. */
  --mark-ring-w: 2px;
  color: var(--f-ink);
  margin: clamp(18px, 2.6vw, 34px) 0 clamp(30px, 3.4vw, 48px);
}
.footer__sign:hover, .footer__sign:focus-visible { color: var(--f-ink); }

/* The 166px outlined wordmark that used to sit here is gone, and with it the
   width arithmetic that kept it inside the gutters. It was a typographic
   stand-in for a logo the site did not have; under the real lockup it said the
   same two words a second time, as decoration. `--f-plate` stays defined on
   .footer — nothing else reads it, but it is one line and it is what a restore
   from git would want.

   Narrow screens get the lockup stacked instead, which is what the full logo
   file does at that proportion too (dir_c(..., stacked=True)). */
@media (max-width: 620px) {
  .footer__sign.lockup {
    --lockup: clamp(64px, 17vw, 86px);
    flex-direction: column;
    align-items: flex-start;
    gap: calc(var(--lockup) * 0.16);
  }
}

/* ------------------------------------------------------- registry strip --- */

.footer__reg {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 32px 40px;
  flex-wrap: wrap;
  padding-top: 24px;
  border-top: 1px solid var(--f-line);
}

.footer__regList {
  display: flex;
  gap: 14px 40px;
  flex-wrap: wrap;
  margin: 0;
}
.footer__regList dt {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  color: var(--f-accent);
  margin-bottom: 5px;
}
.footer__regList dd {
  margin: 0;
  font-size: 12px;
  line-height: 1.55;
  color: var(--f-muted);
  max-width: 34ch;
}

.footer__prefs { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }

/* The band is inverse, so its controls invert with it. */
.footer .lang__btn { color: var(--f-muted); }
.footer .lang__btn[aria-pressed="true"] { color: var(--f-accent); }
.footer .theme-toggle { border-color: var(--f-line); }
.footer .theme-toggle:hover { border-color: var(--f-accent); }
/* The band is page-coloured now, so the toggle is just the toggle. */

.footer__copy a {
  border-bottom: 1px solid var(--hairline);
  padding-bottom: 1px;
  transition: border-color 300ms var(--ease);
}
.footer__copy a:hover { border-bottom-color: var(--ink); }

.footer__copy {
  margin-top: 26px;
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--f-muted);
}

@media (prefers-reduced-motion: reduce) {
  .footer__dot { animation: none !important; }
  .footer__list a::after { transition: none; }
}

/* ------------------------------------------------------------- 20c. ABOUT */
/* The client's "О НАС & СТАНДАРТЫ" brief, built as a document: a heritage
   masthead, a numbered client creed, the certification register (inspection
   marks + the GOST table every position is contracted against), and the
   corridor list. Shares the manifest idiom used by the nav and footer —
   mono indices, hairline rules, sand-gold accents. */

.ab-hero__title {
  font-size: clamp(34px, 5.4vw, 76px);
  font-weight: 600;
  letter-spacing: -0.032em;
  line-height: 1.02;
  margin: 18px 0 clamp(34px, 4vw, 56px);
  max-width: 16ch;
}
.ab-hero__title em { font-style: normal; display: block; color: var(--accent); }

.ab-hero__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
  gap: clamp(34px, 5vw, 84px);
  align-items: start;
}
.ab-hero__copy { display: flex; flex-direction: column; gap: 20px; max-width: 62ch; }
.ab-hero__copy .body-copy { font-size: 16.5px; line-height: 1.72; color: var(--ink-soft); }

/* Founding facts, set as a register rather than prose. */
.ab-facts { margin: 0; border-top: 1px solid var(--hairline); }
.ab-facts > div { padding: 16px 0; border-bottom: 1px solid var(--hairline-soft); }
.ab-facts dt {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 6px;
}
.ab-facts dd { margin: 0; font-size: 14px; line-height: 1.5; color: var(--ink); }

/* ------------------------------------------------------- client creed --- */

.ab-qual__title { max-width: 20ch; margin: 16px 0 clamp(32px, 3.6vw, 52px); }

.ab-qual__list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 clamp(32px, 4vw, 72px);
  counter-reset: none;
}
.ab-qual__item {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  padding: 22px 0;
  border-top: 1px solid var(--hairline-soft);
}
/* The two columns each want a rule above their first row. */
.ab-qual__item:nth-child(-n+2) { border-top-color: var(--hairline); }

.ab-qual__idx {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.08em;
  color: var(--accent);
  padding-top: 4px;
  flex: none;
}
.ab-qual__item p { font-size: 15.5px; line-height: 1.6; color: var(--ink-soft); }

.ab-qual__close {
  margin-top: clamp(30px, 3.4vw, 46px);
  padding-top: 26px;
  border-top: 2px solid var(--accent);
  font-size: clamp(17px, 1.7vw, 21px);
  font-weight: 600;
  letter-spacing: -0.015em;
  line-height: 1.4;
  max-width: 34ch;
}

/* ------------------------------------------------- certification block --- */

.ab-std__title { max-width: 20ch; margin: 16px 0 clamp(32px, 3.6vw, 52px); }
.ab-std__block + .ab-std__block { margin-top: clamp(44px, 5vw, 76px); }

.ab-std__h {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding-bottom: 13px;
  margin-bottom: 24px;
  border-bottom: 1px solid var(--hairline);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
.ab-std__h i {
  font-family: var(--font-mono);
  font-style: normal;
  font-size: 10px;
  font-weight: 500;
  color: var(--accent);
}

.ab-std__marks {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 1px;
  background: var(--hairline-soft);
  border: 1px solid var(--hairline-soft);
  border-radius: var(--radius);
  overflow: hidden;
}
.ab-mark {
  background: var(--tile);
  padding: 22px 20px;
  transition: background 300ms var(--ease);
}
.ab-mark:hover { background: var(--tile-strong); }
.ab-mark__name {
  font-size: 14.5px;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin-bottom: 7px;
}
.ab-mark__gloss { font-size: 12.5px; line-height: 1.55; color: var(--muted); }

.ab-std__note {
  font-size: 14.5px;
  line-height: 1.65;
  color: var(--ink-soft);
  max-width: 62ch;
  margin-bottom: 26px;
}

/* The GOST register — a two-column table that scrolls inside itself rather
   than pushing the page sideways on a phone. */
.ab-gost { overflow-x: auto; -webkit-overflow-scrolling: touch; }
/* Capped measure: across a 1500px shell the two columns would sit at opposite
   edges of the row and stop reading as a pair. */
.ab-gost__table {
  width: 100%;
  max-width: 820px;
  min-width: 380px;
  border-collapse: collapse;
}
.ab-gost__table th {
  text-align: left;
  padding: 0 0 12px;
  border-bottom: 1px solid var(--hairline);
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
.ab-gost__table th:last-child { text-align: right; }
.ab-gost__table td {
  padding: 13px 0;
  border-bottom: 1px solid var(--hairline-soft);
  font-size: 14.5px;
}
.ab-gost__table tbody tr { transition: background 250ms var(--ease); }
.ab-gost__table tbody tr:hover { background: var(--accent-soft); }
.ab-gost__std {
  font-family: var(--font-mono);
  font-size: 12px !important;
  letter-spacing: 0.04em;
  color: var(--accent);
  white-space: nowrap;
  text-align: right;
}

/* ------------------------------------------------------------ corridors --- */

.ab-corr__title { max-width: 18ch; margin: 16px 0 18px; }
.ab-corr__copy { max-width: 56ch; margin-bottom: clamp(30px, 3.4vw, 46px); }

.ab-corr__list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 0 clamp(30px, 3.6vw, 60px);
}
.ab-lane {
  display: flex;
  align-items: baseline;
  gap: 16px;
  padding: 18px 0;
  border-bottom: 1px solid var(--hairline-soft);
}
.ab-lane__idx {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.08em;
  color: var(--accent);
  flex: none;
}
.ab-lane__name { font-size: 15.5px; font-weight: 500; letter-spacing: -0.01em; }

@media (max-width: 860px) {
  .ab-hero__grid { grid-template-columns: 1fr; }
  .ab-qual__list { grid-template-columns: 1fr; }
  .ab-qual__item:nth-child(-n+2) { border-top-color: var(--hairline-soft); }
  .ab-qual__item:first-child { border-top-color: var(--hairline); }
}

/* ------------------------------------------- 20b. SCROLL-DRIVEN ENHANCEMENTS */
/* CSS scroll timelines — zero JS, compositor-only, and skipped entirely by
   browsers without support or by users preferring reduced motion.
   Note: on supporting browsers the category-card art trades its hover zoom
   for a continuous scroll parallax (an animation always wins over a
   transition on the same property).                                          */

@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {

    /* Hero exits with depth: film sinks slower than the page…
       scroll(root) is required — a bare scroll() would bind to .hero,
       which is overflow:hidden and therefore a scroll container with
       zero range, leaving these animations permanently inactive. */
    .hero__layer {
      animation: heroExitMedia linear both;
      animation-timeline: scroll(root);
      animation-range: 0 111.12vh;   /* 100vh / --zoom */
    }

    /* …while the headline lifts away and dissolves. Two rates now, film and
       headline; the third belonged to the sand layer v2 folded into the
       film. */
    .hero__content, .hero__cue {
      animation: heroExitContent linear both;
      animation-timeline: scroll(root);
      animation-range: 0 77.78vh;    /* 70vh / --zoom */
    }

    /* Product tiles settle into place as they enter the viewport. */
    .card__figure {
      animation: figRise linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 65%;
    }

    /* Category art drifts against the scroll — quiet parallax. */
    .catcard__art {
      animation: catDrift linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 100%;
    }
  }
}

@keyframes heroExitMedia   { to { transform: translate3d(0, 9vh, 0); } }
@keyframes heroExitContent { to { opacity: 0; transform: translate3d(0, -7vh, 0); } }
@keyframes figRise {
  from { opacity: 0.55; transform: translate3d(0, 20px, 0) scale(0.985); }
}
@keyframes catDrift {
  from { transform: translate3d(0, 4.5%, 0); }
  to   { transform: translate3d(0, -5.5%, 0); }
}

/* ----------------------------------------------------------------- 21. MOTION */

.fade-up {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 700ms cubic-bezier(0.16, 1, 0.3, 1),
              transform 700ms cubic-bezier(0.16, 1, 0.3, 1);
  will-change: opacity, transform;
}

.fade-up.in-view { opacity: 1; transform: translateY(0); }

.stagger-1 { transition-delay: 90ms; }
.stagger-2 { transition-delay: 180ms; }
.stagger-3 { transition-delay: 270ms; }
.stagger-4 { transition-delay: 360ms; }
.stagger-5 { transition-delay: 450ms; }
.stagger-6 { transition-delay: 540ms; }

/* ---- starfield canvas ---- */
.ac-stars {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  mix-blend-mode: screen;
  will-change: transform;
}

/* ---- word-by-word reveal ---- */
.word-reveal--ready .word-reveal__w {
  display: inline-block;
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 600ms cubic-bezier(0.16, 1, 0.3, 1),
              transform 600ms cubic-bezier(0.16, 1, 0.3, 1);
}
.word-reveal--ready .word-reveal__w--in {
  opacity: 1;
  transform: translateY(0);
}

/* ---- scale-up entrance ---- */
.scale-up {
  opacity: 0;
  transform: scale(0.92);
  transition: opacity 800ms cubic-bezier(0.16, 1, 0.3, 1),
              transform 800ms cubic-bezier(0.16, 1, 0.3, 1);
}
.scale-up--in { opacity: 1; transform: scale(1); }

/* ---- globe glow ---- */
.corridor__globe::before {
  content: '';
  position: absolute;
  inset: -15%;
  border-radius: 50%;
  background: radial-gradient(circle, var(--accent) 0%, transparent 70%);
  opacity: 0.07;
  z-index: -1;
  pointer-events: none;
  transition: opacity 900ms var(--ease);
}
[data-theme="dark"] .corridor__globe::before { opacity: 0.14; }
.corridor__globe.is-live::before {
  animation: globeGlow 4s ease-in-out infinite alternate;
}
@keyframes globeGlow {
  from { opacity: 0.07; }
  to   { opacity: 0.13; }
}
[data-theme="dark"] .corridor__globe.is-live::before {
  animation-name: globeGlowDark;
}
@keyframes globeGlowDark {
  from { opacity: 0.12; }
  to   { opacity: 0.22; }
}

/* Utilities */
.u-mt-s { margin-top: 20px; }
.u-mt-m { margin-top: 36px; }
.u-mt-l { margin-top: 56px; }

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

.skip-link {
  position: absolute;
  left: -9999px; top: 0;
  z-index: 300;
  background: var(--ink);
  color: var(--bg);
  padding: 13px 20px;
  border-radius: var(--radius-s);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.skip-link:focus { left: 12px; top: 12px; }

/* ------------------------------------------------------------- 22. RESPONSIVE */

@media (max-width: 1180px) {
  .grid-products { grid-template-columns: repeat(3, 1fr); }
  .grid-cats { grid-template-columns: repeat(2, 1fr); }
  .footer__cols { grid-template-columns: repeat(2, 1fr); gap: 40px; }
}

@media (max-width: 1024px) {
  /* Full height, not a dropdown. Opening the menu already locks the body to
     no-scroll, so a short panel left the page visible underneath but frozen —
     which reads as a bug. */
  .nav__cta { display: none; }

  /* No column shuffling here any more. The old rule moved the mark to track 2
     to undo the desktop collision, which is why the bar only ever looked right
     below this breakpoint. .nav__lead holds the cluster together at every
     width, so all this needs to do is take the lockup down a size. */
  .nav__inner { gap: 12px; }
  .nav__mark { --lockup: 29px; }

  .feature { grid-template-columns: 1fr; }
  .feature__media { aspect-ratio: 16 / 9; min-height: 0; }
  .contact-grid { grid-template-columns: 1fr; gap: 56px; }
  .qual__item { grid-template-columns: 1fr; gap: 6px; }
  .grid-products { grid-template-columns: repeat(2, 1fr); }


  .shop { grid-template-columns: 1fr; }
  .filters { position: static; }
  .filters__groups { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0 32px; }

  .pdp { grid-template-columns: 1fr; }
  .pdp__stage { position: static; aspect-ratio: 4 / 3.4; }
}

@media (max-width: 860px) {
  .grid-notes { grid-template-columns: 1fr; }
  .drawer { width: 100%; }
  .press__row { gap: 24px 40px; }
}

@media (max-width: 768px) {
  :root { --nav-h: 64px; }

  body { font-size: 15px; }

  /* No min-height floor here: the portrait cut is made to fill the phone, so
     the hero keeps the base 100svh rather than the old 600px block. */
  .hero__content { max-width: 100%; bottom: clamp(52px, 8vh, 92px); }

  /* A 62% window on a tall screen is a slot, not a plate — widen it, let the
     portrait cut breathe, and open a little quicker on a device where the
     whole page is one thumb-flick away. */
  .ac-intro .hero { --wt: 11%; --wb: 15%; --wx: 7%; }
  .hero { --win-open: 780ms; }
  .hero__plate { font-size: 9px; letter-spacing: 0.16em; }

  /* The phone's copy sits lower, so the melt is kept shallower to stay out
     from under the quiet link. */
  .hero__fade { height: 13vh; }
  .hero__copy { font-size: 15px; margin-bottom: 0; }
  .hero__cue { display: none; }
  .hero__meta { font-size: 10px; gap: 9px; margin-bottom: 22px; }
  .hero__sep { display: none; }


  .drawer { top: auto; left: 0; right: 0; bottom: 0;
             height: calc(92vh / var(--zoom)); height: calc(92svh / var(--zoom)); width: 100%;
            transform: translateY(100%); border-radius: 14px 14px 0 0;
            box-shadow: 0 -20px 56px rgba(0, 0, 0, 0.22); }
  .drawer.is-open { transform: translateY(0); }
  .drawer__head { padding: 20px 22px; }
  .drawer__body { padding: 26px 22px 40px; }
  .drawer__meta { grid-template-columns: 1fr; gap: 14px; }

  .field-row { grid-template-columns: 1fr; gap: 0; }

  /* Three labels will not sit side by side at this width — the group wraps to
     one tab per row rather than shrinking the type to nothing. */
  .enq__tabs { grid-auto-flow: row; grid-auto-columns: auto; }
  .enq__tab { text-align: left; padding-left: 14px; }

  /* The masthead unstacks: the desk readout goes left-aligned under the
     claim rather than clinging to a right edge that no longer exists. */
  .footer__top { flex-direction: column; align-items: flex-start; gap: 26px; }
  .footer__claim { max-width: none; }
  .footer__desk { text-align: left; }
  .footer__state, .footer__clock { justify-content: flex-start; }

  .footer__cols { grid-template-columns: 1fr; gap: 34px; }
  .footer__addr, .footer__reply { max-width: none; }

  /* Two words, one per line. 15vw keeps the longer word inside the gutter. */
  .footer__plate { font-size: clamp(34px, 15vw, 96px); line-height: 0.98; white-space: normal; }
  .footer__plate em { display: block; }
  .footer__plate em::before { content: none; }

  .footer__reg { flex-direction: column; gap: 26px; }
  .footer__regList { gap: 16px 28px; }

  .mapframe { height: 300px; }
  .desk__dl > div { grid-template-columns: 1fr; gap: 6px; }

  .filters__groups { grid-template-columns: 1fr; }
  .pdp__buy { flex-wrap: wrap; }
  .pdp__buy .btn { flex-basis: 100%; }
}

@media (max-width: 560px) {
  .nav__side .lang { display: none; }
  .grid-products { grid-template-columns: 1fr; }
  .grid-cats { grid-template-columns: 1fr; }
  .catcard { aspect-ratio: 4 / 4.4; }
}

/* --------------------------------------------------------- 23. REDUCED MOTION */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    transition-delay: 0ms !important;
  }

  .fade-up { opacity: 1; transform: none; }
  .hero__content .reveal { opacity: 1; transform: none; }
  .hero__title .line > span { transform: none; }
  .hero__dot::after { animation: none; }
  .card__quick { opacity: 1; transform: none; }
  .scale-up { opacity: 1; transform: none; }
  .word-reveal--ready .word-reveal__w { opacity: 1; transform: none; }
  .ac-stars { display: none; }
  .corridor__globe::before { animation: none; }
}

/* -------------------------------------------------------------------- 24. PRINT */

@media print {
  .nav, .drawer, .drawer-backdrop, .hero__cue, .hero__video, .theme-toggle { display: none !important; }
  .fade-up { opacity: 1; transform: none; }
}
