/* BONO FOOD — layout and components. One file, both directions.

   Direction-dependent values multiply by var(--dir); everything else is
   direction-neutral and must stay that way, so /ar/ needs no second sheet.

   Colour comes only from tokens.css. Two rules are load-bearing:
     - `color: var(--grass)` is FORBIDDEN anywhere (check_structure enforces
       it). --grass clears AA on one ground by 0.01 and fails the other two.
       --leaf is the green that may carry text on light.
     - --zest is text on --forest and --canopy ONLY. On white it is 1.59:1.
       No scanner can see the ground, so that one is on the author.

   Reveal contract is [data-reveal] + .is-lit, driven by motion.js. Targets
   ship VISIBLE; the hidden pre-reveal state lives behind html.js, so a
   crawler or a failed script still sees every word. */

/* ---------------------------------------------------------- base ---- */
*, *::before, *::after { box-sizing: border-box; }
html, body, h1, h2, h3, p, figure, dl, dd, ul, ol { margin: 0; padding: 0; }
ul, ol { list-style: none; }
html { scroll-behavior: smooth; }

body {
  background: var(--paper);
  color: var(--ink);
  font: 400 17px/1.65 var(--face-body);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
img { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; }

:focus-visible { outline: 3px solid var(--leaf); outline-offset: 3px; border-radius: 2px; }
/* --leaf vanishes on the dark grounds; --zest is 9.32:1 there. */
.hd:not(.is-small) :focus-visible, .hero :focus-visible, .stats :focus-visible,
.cta :focus-visible, .ft :focus-visible, .band :focus-visible {
  outline-color: var(--zest);
}
/* The condensed header is 94% white, so --zest there is 1.59:1 — a focus
   ring nobody can see, failing SC 1.4.11 and 2.4.11. The hover colour was
   already guarded for .is-small further down; the focus ring was not. */
.hd.is-small :focus-visible { outline-color: var(--leaf); }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}
.skip {
  position: absolute; inset-inline-start: 12px; top: -60px; z-index: 100;
  background: var(--canopy); color: var(--paper);
  padding: 12px 18px; font-weight: 600;
  transition: top .2s var(--ease);
}
.skip:focus { top: 12px; }

h1, h2, h3 {
  font-family: var(--face-display);
  font-weight: 800; line-height: .98;
  letter-spacing: var(--track-display);
  text-wrap: balance;
}
.wrap { max-width: var(--wrap); margin-inline: auto; padding-inline: var(--pad); }
.mono {
  font: 500 11px/1 var(--face-mono);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}

.js [data-reveal] {
  opacity: 0;
  transform: translate(calc(10px * var(--dir)), 30px);
  transition: opacity var(--reveal-dur) var(--ease),
              transform var(--reveal-dur) var(--ease);
}
.js [data-reveal].is-lit { opacity: 1; transform: none; }

/* #main, not <html>: it is the common ancestor of every [data-reveal] and a
   SIBLING of .hd, so clipping here cannot reach the header's position:sticky.
   `clip`, not `hidden` — clip never promotes the other axis to auto and never
   creates a scroll container. */
#main { overflow-x: clip; }

/* -------------------------------------------------------- buttons ---- */
.btn {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 15px 26px; border: 0; border-radius: 2px;
  font: 600 15px/1 var(--face-body); cursor: pointer;
  transition: transform .3s var(--ease), background .3s, color .3s, border-color .3s;
}
/* --canopy, NOT --grass: white on --grass is 3.29:1 and fails AA. The
   approved mockup shipped exactly that bug in its header button. */
.btn-solid { background: var(--canopy); color: var(--paper); }
.btn-solid:hover { background: var(--forest); transform: translateY(-3px); }
.btn-zest { background: var(--zest); color: var(--forest); }
.btn-zest:hover { background: var(--paper); transform: translateY(-3px); }
.btn-ghost { border: 1.5px solid rgba(255,255,255,.42); color: var(--paper); }
.btn-ghost:hover { border-color: var(--zest); color: var(--zest); transform: translateY(-3px); }
/* The secondary button on a LIGHT ground. .btn-ghost and .btn-zest are both
   dark-ground-only (a white border and a 1.59:1 highlight respectively), so
   the product pages had no legal second button until this existed.
   --ink on --paper is 16.59:1; --leaf on --paper is 6.11:1. */
.btn-line { border: 1.5px solid var(--line); color: var(--ink); background: var(--paper); }
.btn-line:hover { border-color: var(--leaf); color: var(--leaf); transform: translateY(-3px); }

/* --------------------------------------------------------- header ---- */
.hd {
  position: fixed; inset-block-start: 0; inset-inline: 0; z-index: 60;
  display: flex; align-items: center; gap: clamp(16px, 3vw, 40px);
  padding: 18px var(--pad);
  transition: padding .45s var(--ease), background .45s var(--ease), box-shadow .45s var(--ease);
}
.hd.is-small {
  padding: 9px var(--pad);
  background: rgba(255,255,255,.94);
  backdrop-filter: blur(14px);
  box-shadow: 0 1px 0 var(--line);
}
/* Product and contact pages begin on a light surface. Keeping their header
   transparent made the white primary navigation disappear until the visitor
   scrolled, which is an especially poor first impression on a catalogue. */
.hd--solid {
  background: rgba(255,255,255,.97);
  box-shadow: 0 1px 0 var(--line);
}
.hd-mark { display: flex; align-items: center; line-height: 0; flex: 0 0 auto; }
.hd-mark img {
  height: 52px; width: auto; border-radius: 50%;
  background: var(--paper); padding: 3px;
  box-shadow: 0 2px 14px rgba(0,0,0,.18);
  transition: height .45s var(--ease), box-shadow .45s var(--ease);
}
.hd.is-small .hd-mark img { height: 40px; box-shadow: none; }

.hd-nav, .hd-lang { display: flex; gap: clamp(12px, 2vw, 26px); align-items: center; }
.hd-nav { margin-inline-start: auto; }
.hd-nav a, .hd-lang a {
  font: 600 12.5px/1 var(--face-body);
  letter-spacing: .05em; text-transform: uppercase;
  color: rgba(255,255,255,.86);
  transition: color .45s var(--ease);
}
.hd.is-small .hd-nav a, .hd.is-small .hd-lang a { color: var(--ink-soft); }
.hd--solid .hd-nav a, .hd--solid .hd-lang a { color: var(--ink-soft); }
.hd-nav a:hover, .hd-lang a:hover { color: var(--zest); }
.hd.is-small .hd-nav a:hover, .hd.is-small .hd-lang a:hover { color: var(--leaf); }
.hd--solid .hd-nav a:hover, .hd--solid .hd-lang a:hover { color: var(--leaf); }
.hd-lang a[aria-current="true"] { color: var(--paper); font-weight: 700; }
.hd.is-small .hd-lang a[aria-current="true"] { color: var(--ink); }
.hd--solid .hd-lang a[aria-current="true"] { color: var(--ink); }

/* Arabic header type. Two separate corrections, and the first is the bug:
   `line-height: 1` makes the line box exactly the font size, which is enough
   for Figtree and not for IBM Plex Sans Arabic — the ج of المنتجات and the ص
   of اتصل both drop well below the baseline and were being cut off. Leading,
   not a smaller size, is what fixes clipping; shrinking alone just clips
   proportionally.
   The size step-down is the second, independent correction: Arabic sets
   optically larger than Latin at an identical px value, so 12.5px reads
   heavier here than it does on /en/ and /tr/.
   Scoped to [dir="rtl"] so the Latin trees keep the setting the design was
   drawn at. The header's height comes from the 52px logo, so the taller line
   box changes no layout. */
[dir="rtl"] .hd-nav a,
[dir="rtl"] .hd-lang a { font-size: 11.5px; line-height: 1.7; }
.hd-lang {
  padding-inline-start: clamp(12px, 2vw, 22px);
  border-inline-start: 1px solid rgba(255,255,255,.28);
}
.hd.is-small .hd-lang { border-inline-start-color: var(--line); }
.hd--solid .hd-lang { border-inline-start-color: var(--line); }
.hd .btn { padding: 11px 20px; font-size: 13px; }

/* Below 700px the header WRAPS to a second row rather than hiding anything.
   No hamburger: a scripted disclosure would regress the rule this site holds
   everywhere else — a crawler or a failed script still reaches every word.
   The first build hid .hd-nav outright here and shipped six pages with no
   route at all to Products or Contact. */
@media (max-width: 700px) {
  .hd { flex-wrap: wrap; row-gap: 8px; padding-block: 10px; }
  .hd-nav { margin-inline-start: 0; order: 3; width: 100%; flex-wrap: wrap; gap: 14px; }
  .hd-lang { margin-inline-start: auto; }
  .hd-nav a { font-size: 11.5px; }
  /* An explicit RTL rule is required, not optional: `[dir="rtl"] .hd-nav a`
     above outranks a bare `.hd-nav a` on specificity, so the Latin
     step-down would never have reached the Arabic tree. This is also where
     clipping bit hardest — the nav wraps to its own row here, so a clipped
     descender lands against the row below it. */
  [dir="rtl"] .hd-nav a { font-size: 10.5px; }
  .hd-mark img { height: 42px; }
}

/* ----------------------------------------------------------- hero ---- */
.hero {
  position: relative; min-height: 100svh; display: grid;
  background: var(--forest); overflow: hidden; isolation: isolate;
}
.hero-slides { position: absolute; inset: 0; }
.hero-slides [data-slide] { position: absolute; inset: 0; opacity: 0; transition: opacity var(--slide-fade) var(--ease); }
.hero-slides [data-slide].is-on { opacity: 1; }
.hero-slides img {
  width: 100%; height: 100%; object-fit: cover;
  transform: scale(1.06);
  transition: transform var(--kenburns) linear;
}
.hero-slides [data-slide].is-on img { transform: scale(1.16); }

.hero::before, .hero::after { content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none; }
.hero::before {
  background: linear-gradient(calc(100deg * var(--dir)),
    rgba(6,26,13,.94) 0%, rgba(6,26,13,.72) 38%, rgba(6,26,13,.18) 72%, rgba(6,26,13,.42) 100%);
}
.hero::after {
  inset-block-start: auto; height: 42%;
  background: linear-gradient(transparent, rgba(6,26,13,.82));
}
/* No photography yet: the scrim has nothing to sit on, so the ground carries
   it. This is the state the site ships in until the client sends images, and
   it has to read as deliberate rather than broken. */
.hero[data-no-photography]::before {
  background: radial-gradient(120% 90% at calc(50% + 22% * var(--dir)) 0%, var(--canopy), var(--forest) 62%);
}

.hero-inner {
  position: relative; z-index: 2; align-self: end; width: 100%;
  padding-block: calc(var(--header-h) + 40px) clamp(64px, 12vh, 132px);
}
.hero-kicker { color: var(--zest); display: flex; align-items: center; gap: 12px; margin-bottom: 20px; min-height: 11px; }
.hero-kicker::before { content: ""; width: 34px; height: 2px; background: var(--zest); flex: 0 0 auto; }
/* `.hero-h`, NOT `.hero h1`. The visible headline is a <p> (the page's real
   <h1> is .sr-only, because the headline text changes per slide and a
   document outline must not). The mockup's selector was `.hero h1` and was
   lifted verbatim onto restructured markup, so it matched NOTHING: the
   headline rendered at inherited 17px in --ink on --forest, 1.12:1, on all
   three homepages. Font-family/weight/tracking come from the h1,h2,h3 rule
   above, which the fallback <h2> still matches; the <p> needs them stated. */
.hero-h {
  font-family: var(--face-display);
  font-weight: 800; line-height: .98;
  letter-spacing: var(--track-display);
  text-wrap: balance;
  font-size: clamp(44px, 8.6vw, 124px);
  color: var(--paper);
  max-width: 15ch;
  margin-bottom: 22px;
}
/* Exactly one headline is ever visible. hero.js hides the authored fallback
   the moment it writes a real one, and shows it again if it cannot. Doing
   this in CSS behind html.js was rejected: it would blank the headline
   whenever the script fails, which is the failure Ruling 17 exists to stop. */
.hero-h[hidden] { display: none; }

/* ------------------------------------------- Arabic typography ---- */
/* Every leading decision for the RTL tree lives here, because they all fix
   ONE defect measured across the three Arabic pages: this stylesheet sets
   display type at `line-height: .98` and UI type at `/1`, which is a line box
   no taller than the font. Bricolage Grotesque and Figtree have nothing below
   the baseline to lose. IBM Plex Sans Arabic does — ع, ج, ص, م and ن all
   descend — so the glyphs overflowed their line box, and wherever an ancestor
   was `overflow: hidden` (.hero, .band, .cta, .cat, .prod) they were CUT.

   Measured before, at 1280px: 26 Arabic elements overflowed, worst 22.8px on
   a section h2 (88px of ink in a 65px box). The audit that produced those
   numbers is in the README's browser checklist.

   Sizes drop as well as leading only where Arabic genuinely swamped a frame
   drawn for Latin: Arabic sets optically larger per em and has no uppercase,
   so an identical px value reads roughly a third bigger. */
[dir="rtl"] h1,
[dir="rtl"] h2,
[dir="rtl"] h3 { line-height: 1.34; }

[dir="rtl"] .hero-h {
  font-size: clamp(30px, 5.4vw, 74px);
  line-height: 1.34;
  /* ch is the width of "0", so the Latin 15ch wraps Arabic far too early. */
  max-width: 18ch;
}
[dir="rtl"] .hero-kicker,
[dir="rtl"] .mono { line-height: 1.5; }
[dir="rtl"] .btn { line-height: 1.5; }
[dir="rtl"] .stats .n { line-height: 1.15; }
[dir="rtl"] .cat h3 { font-size: 22px; line-height: 1.3; }
[dir="rtl"] .cat-blurb { line-height: 1.6; }
[dir="rtl"] .prod h3 { font-size: 18px; line-height: 1.35; }
[dir="rtl"] .prod-desc { line-height: 1.75; }
[dir="rtl"] .chip { line-height: 1.6; }
[dir="rtl"] .ft h3 { line-height: 1.5; }
[dir="rtl"] .spec-row { line-height: 1.6; }
/* Arabic tashkeel stack ABOVE the letter, so a heading carrying a shadda
   ("مسجَّلة") has a taller ascent than the same words without one — measured
   62.6px of ascent in an 89.2px box, reaching 2.2px past the top. The kicker
   above it sat at a 0px gap, so the two could touch. This is the mirror of
   the descender problem and needs space, not leading. */
[dir="rtl"] .sec-head .k { margin-block-end: 8px; }

/* ------------------------------------------ Turkish typography ---- */
/* Same defect as the Arabic block above, smaller in degree, and it needed
   measuring to find because Turkish and English share a font stack and a
   direction — nothing about the markup suggests they would differ.

   They differ because of the diacritics. ş and ç hang a cedilla BELOW the
   baseline; İ and ğ add a dot or breve ABOVE it. Measured at 1280px:
     /tr/          h2 "Her ürün hattı..."  62px of ink in a 55.2px box
     /tr/contact/  h1 "İletişim"           39px of ink in a 33.3px box
   Seven elements on /tr/ were being CUT inside .band, .cta, .cat, .prod
   and .hero.

   ENGLISH IS NOT TOUCHED, and that is a measurement not an assumption: all
   three /en/ pages audited 0 overflowing, 0 cut. Bricolage Grotesque and
   Figtree were drawn for it and .98 is a deliberate, working choice there.
   Scoped to [lang="tr"] so it stays that way. */
[lang="tr"] h1,
[lang="tr"] h2,
[lang="tr"] h3 { line-height: 1.2; }
[lang="tr"] .mono { line-height: 1.45; }
[lang="tr"] .btn { line-height: 1.4; }
[lang="tr"] .ft h3 { line-height: 1.45; }
[lang="tr"] .chip { line-height: 1.45; }
[lang="tr"] .hd-nav a,
[lang="tr"] .hd-lang a { line-height: 1.5; }
.hero-sub { color: rgba(255,255,255,.84); font-size: clamp(16px, 1.5vw, 20px); max-width: 52ch; margin-bottom: 36px; }
.hero-btns { display: flex; gap: 14px; flex-wrap: wrap; }

/* Per-word mask reveal, built by hero.js as .rv > span.
   A CSS ANIMATION, not a class the script has to remember to add. The first
   version started each word at translateY(105%) and revealed it when JS set
   .is-lit on the next animation frame — which meant a dropped frame, a
   throttled background tab, or any script failure left the entire headline
   invisible with no error. `animation ... both` plays once on insertion and
   ends at the visible state, so the worst case is a word that appears
   without moving. Fail visible, never fail blank. */
.rv { display: inline-block; overflow: hidden; vertical-align: bottom; }
.rv > span { display: inline-block; animation: word-rise .78s var(--ease) both; }
@keyframes word-rise { from { transform: translateY(105%); } to { transform: translateY(0); } }

.hero-ghost {
  position: absolute; inset-inline-end: var(--pad); inset-block-end: clamp(48px, 9vh, 104px);
  z-index: 2; font-family: var(--face-display); font-weight: 800;
  font-size: clamp(88px, 16vw, 232px); line-height: .8;
  color: transparent; -webkit-text-stroke: 1.5px rgba(255,255,255,.22);
  pointer-events: none; user-select: none; font-variant-numeric: tabular-nums;
}
.hero-dots {
  position: absolute; inset-inline-start: var(--pad); inset-block-end: clamp(24px, 4vh, 46px);
  z-index: 3; display: flex; gap: 10px;
}
.hero-dots button {
  width: 52px; height: 3px; padding: 0; border: 0; cursor: pointer;
  background: rgba(255,255,255,.28); position: relative;
}
/* The bar is 3px tall; this gives it a real 27px tap target without moving
   anything visually. */
.hero-dots button::after { content: ""; position: absolute; inset: -12px -3px; }
.hero-dots button i { position: absolute; inset: 0; overflow: hidden; background: var(--zest); transform: scaleX(0); transform-origin: calc(50% - 50% * var(--dir)) 0; }
.hero-dots button.is-on i { animation: dot-fill var(--slide-hold) linear forwards; }
@keyframes dot-fill { to { transform: scaleX(1); } }

/* ---------------------------------------------------------- stats ---- */
.stats { background: var(--forest); color: var(--paper); padding-block: clamp(48px, 7vw, 88px); }
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: clamp(24px, 4vw, 56px); }
.stats .n {
  font-family: var(--face-display); font-weight: 800;
  font-size: clamp(42px, 5.6vw, 78px); line-height: .9;
  color: var(--zest); letter-spacing: -.04em; font-variant-numeric: tabular-nums;
}
/* .9 above is drawn for big numerals, which have no ascender or descender to
   lose. .is-text holds a WORD — and specifically İstanbul, whose dotted İ
   overflowed that box by 1.2px on mobile. It appears on all three trees, not
   just /tr/, because company facts are carried across trees unchanged, so
   this fix is not language-scoped either. */
.stats .n.is-text { font-size: clamp(24px, 3vw, 42px); line-height: 1.15; }
.stats .l { margin-top: 10px; color: rgba(255,255,255,.66); }

/* ------------------------------------------------- page banner ---- */
/* The photographic strip at the head of products.html and of each category
   page. Same scrim geometry as .hero, a third of the height: these pages are
   destinations a buyer has already chosen, so the frame introduces them
   rather than selling to them. */
.phero {
  position: relative; background: var(--forest); color: var(--paper);
  overflow: hidden; isolation: isolate;
  min-height: clamp(300px, 40vh, 440px); display: grid; align-items: end;
}
.phero > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.phero::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(calc(100deg * var(--dir)),
    rgba(6,26,13,.95) 0%, rgba(6,26,13,.82) 44%, rgba(6,26,13,.5) 100%);
}
.phero-in {
  position: relative; z-index: 2; width: 100%;
  padding-block: calc(var(--header-h) + 34px) clamp(36px, 6vh, 68px);
}
.phero h1 { font-size: clamp(34px, 5.6vw, 76px); color: var(--paper); max-width: 17ch; }
.phero p { color: rgba(255,255,255,.84); max-width: 58ch; margin-top: 18px; }
[dir="rtl"] .phero h1 { font-size: clamp(26px, 4.4vw, 56px); max-width: 20ch; }

/* ------------------------------------------------- breadcrumbs ---- */
/* A real <nav><ol>, mirrored by the BreadcrumbList in the page's JSON-LD.
   The separator is a ::before on every item but the first, so it is never a
   text node a screen reader has to read out, and it flips with direction. */
.crumbs { margin-bottom: 22px; font-size: 13px; }
.crumbs ol { display: flex; flex-wrap: wrap; align-items: center; gap: 0 8px; }
.crumbs li { display: flex; align-items: center; gap: 8px; color: var(--ink-soft); }
.crumbs li + li::before {
  content: "\002F"; color: var(--line); font-size: 12px;
  transform: scaleX(var(--dir)); display: inline-block;
}
.crumbs a:hover { color: var(--leaf); text-decoration: underline; }
.crumbs [aria-current="page"] { color: var(--ink); font-weight: 600; }
/* On the banner the same component sits on --forest, where --ink-soft is
   2.29:1 and --leaf all but vanishes. */
.phero .crumbs li { color: rgba(255,255,255,.82); }
.phero .crumbs li + li::before { color: rgba(255,255,255,.42); }
.phero .crumbs a:hover { color: var(--zest); }
.phero .crumbs [aria-current="page"] { color: var(--paper); }

/* -------------------------------------------------- section frame ---- */
.sec { padding-block: clamp(68px, 10vw, 136px); }
/* The FIRST section of a page that has no .phero above it. The header is
   position:fixed and measures ~88px on desktop and ~90px wrapped at 375px,
   so .sec's own clamp floor of 68px is not enough to clear it — the kicker
   of the first section slid under the header on narrow screens. Stated in
   header units rather than guessed, so it stays correct if --header-h moves. */
/* MEASURED at 375px: the header renders 82px on /en/, 87px on /tr/ and 88px
   on /ar/ once it wraps to two rows, against a --header-h of 78px. A 22px
   floor left the Arabic product pages 12px of clearance; 32px puts every
   tree above 20px. */
.sec-top { padding-block-start: calc(var(--header-h) + clamp(32px, 4vw, 54px)); }
.sec-stone { background: var(--stone); }
.sec-head { display: grid; gap: 18px; margin-bottom: clamp(32px, 5vw, 62px); }
@media (min-width: 900px) { .sec-head { grid-template-columns: 1.15fr .85fr; align-items: end; } }
.sec-head .k { color: var(--leaf); }
.sec-head h2 { font-size: clamp(32px, 5.2vw, 72px); }
.sec-head p { color: var(--ink-soft); max-width: 46ch; }
.lede { color: var(--ink-soft); max-width: 58ch; }

/* ----------------------------------------------------- categories ---- */
.cats { display: grid; gap: 14px; grid-template-columns: 1fr; }
/* Five categories are a fixed part of the range. A deliberate five-up grid
   at desktop removes the accidental four-plus-one orphan card, while the
   intermediate three-up composition keeps the cards comfortably readable. */
@media (min-width: 700px) { .cats { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1100px) { .cats { grid-template-columns: repeat(5, minmax(0, 1fr)); } }
.cat { position: relative; aspect-ratio: 4/5; overflow: hidden; background: var(--forest); display: block; }
.cat img { width: 100%; height: 100%; object-fit: cover; transition: transform 1.1s var(--ease); }
.cat:hover img, .cat:focus-visible img { transform: scale(1.09); }
/* MEASURED, not guessed. The blurb added below pushed the text block to 65%
   of the card, so its first line landed at 57-79% down — where the old
   `transparent 34%` ramp was still only 31-51% opaque. Over the pale oils
   photograph that measured 4.43:1, under the 4.5:1 AA floor for 14px text.
   A photo overlay cannot be checked by check_contrast.py, which compares
   static token pairs; this was caught by sampling the composited pixels in
   the browser, and must be re-measured there if either ramp or copy changes. */
.cat::after { content: ""; position: absolute; inset: 0; background: linear-gradient(transparent 16%, rgba(6,26,13,.5) 42%, rgba(6,26,13,.94)); }
.cat-tx { position: absolute; inset-block-end: 0; inset-inline: 0; z-index: 2; padding: 22px; color: var(--paper); transition: transform .55s var(--ease); }
.cat:hover .cat-tx, .cat:focus-visible .cat-tx { transform: translateY(-8px); }
.cat h3 { font-size: 25px; margin-bottom: 6px; }
.cat .c { color: var(--zest); font: 500 11px/1 var(--face-mono); letter-spacing: .14em; text-transform: uppercase; }
/* The blurb sits over the same dark scrim as the heading, so it takes --paper
   at reduced opacity rather than --ink-soft, which is a light-ground colour
   and would be all but invisible here. */
.cat-blurb {
  margin: 0; font-size: 14px; line-height: 1.45;
  color: rgba(255,255,255,.93);
  /* Bounded so a longer blurb at handover cannot push the text block back up
     the card into the transparent part of the ramp. */
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden;
}
.cat-blurb[data-pending] { color: var(--zest); }

/* ----------------------------------------------------------- band ---- */
.band { position: relative; min-height: clamp(400px, 56vh, 600px); display: grid; place-items: center; overflow: hidden; background: var(--forest); }
.band img { position: absolute; inset-block: -14%; inset-inline: 0; width: 100%; height: 128%; object-fit: cover; will-change: transform; }
.band::after { content: ""; position: absolute; inset: 0; background: linear-gradient(rgba(6,26,13,.78), rgba(6,26,13,.86)); }
.band-c { position: relative; z-index: 2; text-align: center; color: var(--paper); padding-inline: var(--pad); }
.band .k { color: var(--zest); margin-bottom: 18px; }
.band h2 { font-size: clamp(28px, 4.4vw, 60px); max-width: 19ch; margin-inline: auto; }

/* ----------------------------------------------------------- rail ---- */
.rail { display: flex; gap: 14px; overflow-x: auto; padding-bottom: 18px; scroll-snap-type: x mandatory; }
.rail::-webkit-scrollbar { height: 4px; }
.rail::-webkit-scrollbar-thumb { background: var(--grass); }
/* Category filter chips. --leaf, not --grass: --grass is 3.29:1 on white and
   is decoration-only in this palette (tokens.css says so, check_contrast.py
   enforces it). The active chip inverts to --forest/--paper, which measures
   14.84:1 — the same pair the hero uses. */
.filters {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin-block-end: 22px;
}
.chip {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 9px 15px; border: 1px solid var(--line); border-radius: 999px;
  background: var(--paper); color: var(--ink-soft);
  font: 500 14px/1 var(--face-body); cursor: pointer;
  transition: background .25s var(--ease), color .25s var(--ease), border-color .25s var(--ease);
}
.chip:hover { border-color: var(--leaf); color: var(--leaf); }
.chip:focus-visible { outline: 2px solid var(--leaf); outline-offset: 2px; }
.chip.is-on { background: var(--forest); border-color: var(--forest); color: var(--paper); }
.chip-n {
  font: 500 12px/1 var(--face-mono); font-variant-numeric: tabular-nums;
  color: var(--ink-soft); opacity: .7;
}
.chip.is-on .chip-n { color: var(--zest); opacity: 1; }

/* The products page runs the same .prod card as the home rail, but as a grid
   rather than a horizontal scroller: a catalogue is for comparing lines
   side by side, and a rail hides two thirds of them off-screen. `flex` on
   .prod is simply ignored on a grid item, so the card needs no variant. */
.pgrid { display: grid; gap: 14px; grid-template-columns: 1fr; }
@media (min-width: 620px)  { .pgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .pgrid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

/* An <a> since every line gained a page of its own — `display: block` is
   NOT redundant even though the card is always a flex or grid item today:
   an inline <a> that later lands in a plain block container would collapse
   its own padding and margins. */
.prod { display: block; flex: 0 0 296px; scroll-snap-align: start; background: var(--stone); overflow: hidden; }
.prod .ph { aspect-ratio: 3/2; overflow: hidden; background: var(--forest); }
.prod img { width: 100%; height: 100%; object-fit: cover; transition: transform 1s var(--ease); }
.prod:hover img, .prod:focus-visible img { transform: scale(1.07); }
.prod .b { padding: 20px; }
.prod h3 { font-size: 20px; margin-bottom: 8px; transition: color .3s var(--ease); }
/* The only signal that the card is a link, so it must survive keyboard
   focus as well as hover. --leaf on --stone is 5.50:1. */
.prod:hover h3, .prod:focus-visible h3 { color: var(--leaf); }
/* The brand read off the client's own pack. --leaf on --stone, 5.50:1 at
   11px — AA needs 4.5:1 for text this size, so it clears with margin. */
.prod-brand { color: var(--leaf); margin: 0 0 6px; }
.prod-brand[data-pending] { color: var(--amber-ink); }
/* --ink-soft on --stone measures 6.52:1 — an already-checked pair, so the
   description needs no new colour. Sized down from body copy because a card
   is scanned, not read. */
.prod-desc {
  margin: 0 0 14px; font-size: 14.5px; line-height: 1.5;
  color: var(--ink-soft);
}

/* -------------------------------------------- one product's page ---- */
/* The packshot on the left, everything a buyer prices from on the right.
   Deliberately NOT a gallery: the client sent one photograph per line, and a
   thumbnail rail with one thumbnail in it advertises the eleven frames that
   are missing. It becomes a gallery the day a second angle arrives. */
.pdp { display: grid; gap: clamp(26px, 4vw, 54px); align-items: start; }
@media (min-width: 900px) { .pdp { grid-template-columns: 1.02fr .98fr; } }
/* --stone, matching the packshot's own white-padded frame: the photograph is
   staged on white by derive_media.py, so a --forest well would ring it. */
.pdp-media { background: var(--stone); overflow: hidden; }
.pdp-media img { width: 100%; height: auto; }
.pdp-brand { color: var(--leaf); margin-bottom: 12px; }
.pdp h1 { font-size: clamp(30px, 4.4vw, 58px); }
.pdp-lede { color: var(--ink-soft); margin-top: 16px; }
.pdp .spec-table { margin-top: 26px; }
.pdp-cta { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 28px; }
.pdp-note { margin-top: 16px; font-size: 13.5px; color: var(--ink-soft); max-width: 46ch; }

/* Applications and handling, side by side on --stone. */
.facts { display: grid; gap: clamp(28px, 4vw, 60px); }
@media (min-width: 820px) { .facts { grid-template-columns: 1.15fr .85fr; } }
.facts h2 { font-size: clamp(21px, 2.4vw, 30px); margin-bottom: 18px; }
.uses { display: flex; flex-wrap: wrap; gap: 8px; }
.uses li {
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--paper); color: var(--ink);
  padding: 9px 15px; font-size: 14px; line-height: 1.35;
}
.facts p { color: var(--ink-soft); max-width: 44ch; }
[dir="rtl"] .facts h2 { line-height: 1.34; }
[lang="tr"] .facts h2 { line-height: 1.2; }

/* ---------------------------------------------------- spec tables ---- */
.spec-table { display: grid; }
.spec-row { display: grid; grid-template-columns: 1fr auto; gap: 8px 16px; padding: 9px 0; border-block-start: 1px solid var(--line); font-size: 14px; }
.spec-row > span:first-child, .spec-row > dt { color: var(--ink-soft); }
.spec-val { font-variant-numeric: tabular-nums; text-align: end; }
.tgrid { display: grid; gap: 1px; background: var(--line); grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }
.tgrid > div { background: var(--stone); padding: 26px; }
.tgrid dt { color: var(--leaf); margin-bottom: 8px; }
.tgrid dd { font-family: var(--face-display); font-weight: 600; font-size: 19px; letter-spacing: -.02em; }

/* -------------------------------------------------------- partners ---- */
.mq { overflow: hidden; padding-block: 32px; border-block: 1px solid var(--line); --gap: clamp(40px, 6vw, 88px); }
.mq-track { display: flex; gap: var(--gap); width: max-content; animation: mq-slide 34s linear infinite; align-items: center; }
/* The -50% formula only loops seamlessly when the set is rendered TWICE;
   app.js renders it once, so the track shuttled and snapped. It now emits a
   duplicate marked aria-hidden, and this guards the geometry. */
.mq:hover .mq-track, .mq:focus-within .mq-track { animation-play-state: paused; }
@keyframes mq-slide { to { transform: translateX(calc(-1 * (50% + var(--gap) / 2) * var(--dir))); } }
.mq img { height: 42px; width: auto; opacity: .62; filter: grayscale(1); transition: opacity .4s, filter .4s; }
.mq img:hover { opacity: 1; filter: none; }

/* ------------------------------------------------------------ cta ---- */
.cta { background: var(--forest); color: var(--paper); padding-block: clamp(68px, 10vw, 136px); position: relative; overflow: hidden; }
.cta::after {
  content: ""; position: absolute; width: 60vw; height: 60vw;
  inset-inline-end: -18vw; inset-block-start: -22vw; border-radius: 50%;
  background: radial-gradient(circle, rgba(106,156,21,.34), transparent 66%);
  pointer-events: none;
}
.cta-in { position: relative; z-index: 2; display: grid; gap: 34px; }
@media (min-width: 920px) { .cta-in { grid-template-columns: 1.1fr .9fr; align-items: center; } }
.cta h2 { font-size: clamp(32px, 5.2vw, 70px); }
.cta .k { color: var(--zest); margin-bottom: 16px; }
.cta p { color: rgba(255,255,255,.72); max-width: 44ch; margin-top: 16px; }
.cta .spec-row { border-block-start-color: rgba(255,255,255,.16); }
.cta .spec-row > span:first-child { color: rgba(255,255,255,.6); }

/* --------------------------------------------------------- footer ---- */
.ft { background: #071F0E; color: rgba(255,255,255,.6); padding-block: clamp(48px, 7vw, 82px) 32px; font-size: 14px; }
.ft-top { display: grid; gap: 32px; grid-template-columns: repeat(auto-fit, minmax(185px, 1fr)); margin-bottom: 42px; }
.ft h3 { font: 600 12px/1 var(--face-body); letter-spacing: .1em; text-transform: uppercase; color: var(--paper); margin-bottom: 14px; }
.ft ul { display: grid; gap: 9px; }
.ft a:hover { color: var(--zest); }
.ft-base { border-block-start: 1px solid rgba(255,255,255,.12); padding-top: 20px; display: flex; flex-wrap: wrap; gap: 14px; justify-content: space-between; font-size: 12.5px; }

/* --------------------------------------------------- PENDING flags ----
   HAZARD: any rule that sets its own `color` and can ALSO land on a
   [data-pending] element wins at equal (0,1,0) specificity by source order.
   Before adding one, check whether it can co-occur; if so, add a matching
   `<selector>[data-pending]` restating this block. Four separate bugs of
   exactly this shape shipped in the first build. */
[data-pending] {
  background: var(--amber-bg);
  color: var(--amber-ink);
  border: 1px dashed var(--amber-line);
  padding: 2px 7px;
  font: 500 13px/1.35 var(--face-mono);
  border-radius: 2px;
  display: inline-block;
}
.spec-val[data-pending] { color: var(--amber-ink); text-align: start; }
.lede[data-pending] { color: var(--amber-ink); }
.certs li[data-pending] { background: var(--amber-bg); border: 1px dashed var(--amber-line); padding: 2px 7px; font-size: 13px; }

/* ------------------------------------------------------ direction ---- */
/* Latin runs inside Arabic must not reorder the Arabic around them. A CLASS
   selector, so it applies to any element — check_structure's bidi scan is
   tag-agnostic for exactly that reason. */
.lat { unicode-bidi: isolate; }
.lat[data-pending] { font-family: var(--face-mono); }
[dir="rtl"] .spec-val.lat { direction: ltr; text-align: start; }

/* ------------------------------------------------- reduced motion ---- */
/* Reduced motion stops the marquee — inside overflow:hidden that left the
   later logos permanently out of reach at 375px. Scroll it instead: the
   animation is gone, the content is not.
   `overflow-x: auto` is the WHOLE fix. An earlier version of this block also
   set `.mq-track { width: auto }`, which erased all six partner logos on
   every homepage at every width: `width:auto` makes the flex track fill .mq,
   and `img { max-width: 100% }` above then becomes a cyclic percentage that
   zeroes each item's min-content floor. Measured figure widths [0,0,0] while
   naturalWidth stayed 250 — the images loaded and were crushed. The track
   keeps `width: max-content`. */
@media (prefers-reduced-motion: reduce) {
  .mq { overflow-x: auto; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  html { scroll-behavior: auto; }
  .js [data-reveal] { opacity: 1; transform: none; }
  .hero-slides img, .hero-slides [data-slide].is-on img { transform: none; }
  .rv > span { transform: none; }
  .mq-track { animation: none; }
}
