/* ============================================================================
   SignalMatrix — Sounding.

   Implements DESIGN.md. Where this file and DESIGN.md disagree, DESIGN.md is
   right and this file is a defect.

   THREE STANDING RULES, because they are the ones that erode:

   1. LOGICAL PROPERTIES ONLY. No `left`, `right`, `margin-left`, `text-align:
      left`. A physical direction looks correct in English and silently
      reverses the page in Arabic. Where CSS has no logical form
      (transform-origin, clip-path), use the --origin-start / --clip-* tokens
      defined in :root and flipped once under [dir="rtl"].

   2. RULES AND FIELDS MOVE; WORDS AND NUMBERS DO NOT. Nothing set in
      IBM Plex Mono ever animates. No fade-up on body copy, tables or panels.

   3. ZERO RADIUS, NO SHADOW, NO GRADIENT. All three absent by decision.
   ========================================================================== */

/* --- Fonts ---------------------------------------------------------------
   Self-hosted, subset. No CDN: the page ships from one Worker, and a
   third-party font request is an own goal for a company selling sovereignty.

   NOTE — the two Arabic faces are declared here but their .woff2 files are
   not yet in fonts/. Until they are, Arabic falls through to the system
   Naskh/sans in the stacks below, which is legible but not the design. See
   PLAN.md Phase 8.                                                        */

@font-face {
  font-family: 'Newsreader'; font-style: normal; font-weight: 400 600;
  font-display: swap; src: url('fonts/Newsreader-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212;
}
@font-face {
  font-family: 'Newsreader'; font-style: normal; font-weight: 400 600;
  font-display: swap; src: url('fonts/Newsreader-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-024F, U+1E00-1EFF, U+2020, U+20A0-20AB, U+2113;
}
@font-face {
  font-family: 'Archivo'; font-style: normal; font-weight: 400 600;
  font-display: swap; src: url('fonts/Archivo-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+2000-206F, U+20AC, U+2122;
}
@font-face {
  font-family: 'Archivo'; font-style: normal; font-weight: 400 600;
  font-display: swap; src: url('fonts/Archivo-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB;
}
@font-face {
  font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 400;
  font-display: swap; src: url('fonts/IBMPlexMono-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+2000-206F, U+20AC, U+2122;
}
@font-face {
  font-family: 'IBM Plex Mono'; font-style: normal; font-weight: 400;
  font-display: swap; src: url('fonts/IBMPlexMono-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-024F, U+1E00-1EFF;
}
/* ▲ ARABIC FONTS — COMMENTED OUT UNTIL THE FILES ARE COMMITTED. ▲
   Amiri-arabic.woff2 and IBMPlexSansArabic-arabic.woff2 are not in fonts/
   yet, and a live @font-face pointing at a missing file 404s on every Arabic
   page load. The fallback stacks in :root[lang='ar'] already name Noto Naskh
   Arabic and the system faces, so Arabic renders — just not in the designed
   type. Uncomment both blocks and the two <link rel="preload"> tags in
   ar/index.html the moment the files land. Do not ship Arabic to an investor
   before that: the type IS part of the claim (DESIGN.md § Bilingual).

@font-face {
  font-family: 'Amiri'; font-style: normal; font-weight: 400 700;
  font-display: swap; src: url('fonts/Amiri-arabic.woff2') format('woff2');
  unicode-range: U+0600-06FF, U+0750-077F, U+08A0-08FF, U+FB50-FDFF, U+FE70-FEFF;
}
@font-face {
  font-family: 'IBM Plex Sans Arabic'; font-style: normal; font-weight: 400 600;
  font-display: swap; src: url('fonts/IBMPlexSansArabic-arabic.woff2') format('woff2');
  unicode-range: U+0600-06FF, U+0750-077F, U+08A0-08FF, U+FB50-FDFF, U+FE70-FEFF;
}
*/

/* --- Tokens --------------------------------------------------------------- */

:root {
  color-scheme: light dark;

  /* Colour — two palettes, one set of tokens. The OCEAN palette (Dusk Blue
     3D5A80 · Powder Blue 98C1D9 · Light Cyan E0FBFC · Burnt Peach EE6C4D ·
     Jet Black 293241) is the DEFAULT; [data-palette='classic'] below
     restores the original DESIGN.md sunset ramp. Depth assignment, top to
     bottom of the stack: Light Cyan, Burnt Peach, Powder Blue, Dusk Blue,
     Jet Black — the peach sits second because --tertiary is both the accent
     and the signal band, exactly as coral was. Inks are derived, not from
     the swatch: #AD3E1C is Burnt Peach darkened to 5.6:1 on paper. */
  --primary: #293241;
  --secondary: #3D5A80;
  --tertiary: #EE6C4D;
  --tertiary-ink: #AD3E1C;
  --stratum-mid: #98C1D9;
  --stratum-top: #E0FBFC;
  --selection-bg: #98C1D9;
  /* Depth-3 act band ink: Powder Blue needs dark ink (8.9:1) where the
     classic rosewood needed paper (4.9:1). */
  --band-mid-ink: #121C2A;
  --neutral: #F5EDE7;
  --surface: #FBF6F1;
  --on-surface: #121C2A;
  --border: #B3BBC5;
  --caution: #985A2C;

  /* The ground descent — stock range only. The saturated ramp lives in the
     act bands; a ground that reached it would fail AA against the ink.
     Darkest step measured at 14.3:1 against --on-surface. */
  --ground-0: #FBF6F1;
  --ground-1: #F7F1EB;
  --ground-2: #F2ECE6;
  --ground-invert: #0D1217;
  --ground-invert-ink: #EFE6DF;

  /* Typeface stacks. Arabic first in the Arabic build (set below) so the
     Arabic face wins for shared codepoints like digits' spacing context. */
  --font-argument: 'Newsreader', 'Source Serif 4', Georgia, serif;
  --font-apparatus: 'Archivo', 'Helvetica Neue', Arial, sans-serif;
  --font-provenance: 'IBM Plex Mono', ui-monospace, SFMono-Regular, monospace;
  --font-reading: var(--font-apparatus);
  --font-source: var(--font-provenance);

  /* Bilingual derivation — DESIGN.md § Bilingual & Direction.
     One systematic transform: +8% size, +12% leading, tracking zeroed. */
  --ar-size: 1;
  --ar-lead: 1;
  --track: 1;
  --argument-scale: 1;
  --meta-scale: 1;
  --reading-scale: 1;
  --source-scale: 1;
  --ui-scale: 1;
  --measure-prose: 66ch;

  /* Direction. CSS has no logical form for transform-origin or clip-path, so
     these are the single place direction is expressed physically. */
  --dir: 1;
  --origin-start: left;
  --clip-hidden: inset(0 100% 0 0);
  --clip-shown: inset(0 0 0 0);

  /* Spacing — 8px base, 4px half-step inside chips and cells */
  --xs: 4px; --sm: 8px; --md: 16px; --lg: 32px;
  --xl: 64px; --2xl: 112px; --3xl: 176px;
  --gutter: 32px; --margin: 72px; --canvas: 1440px;

  /* Motion — DESIGN.md § Motion. No spring, no overshoot, no bounce. */
  --ease-enter: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-move: cubic-bezier(0.25, 1, 0.5, 1);
  --ease-press: cubic-bezier(0.40, 0.05, 0.20, 1);
  --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
  --t-state: 120ms;
  --t-rule: 420ms;
  --t-press: 560ms;
  --t-band: 320ms;
  --t-fade: 180ms;
  --stagger: 45ms;
}

/* The classic sunset ramp (DESIGN.md § Colors), kept switchable. The chrome
   button and ?palette=classic set this attribute; ?palette=ocean or nothing
   yields the ocean default above. */
:root[data-palette='classic'] {
  --primary: #355070;
  --secondary: #6D597A;
  --tertiary: #E56B6F;
  --tertiary-ink: #973242;
  --stratum-mid: #B56576;
  --stratum-top: #EAAC8B;
  --selection-bg: #EAAC8B;
  --band-mid-ink: #FBF6F1;
}

:root[dir='rtl'] {
  --dir: -1;
  --origin-start: right;
  --clip-hidden: inset(0 0 0 100%);
}

:root[lang='ar'] {
  --ar-size: 1.01;
  --ar-lead: 1.15;
  --track: 0;               /* tracking is a Latin device; it breaks the joins */
  --argument-scale: 0.9;
  --meta-scale: 1.08;
  --reading-scale: 1.13;
  --source-scale: 1.12;
  --ui-scale: 1.1;
  --measure-prose: 60ch;
  --font-argument: 'Noto Naskh Arabic', 'Arabic Typesetting', 'Amiri', serif;
  --font-apparatus: 'Noto Sans Arabic', 'IBM Plex Sans Arabic', 'Tahoma', 'Segoe UI', sans-serif;
  /* Separate reading copy from the display face so Arabic paragraphs carry
     more color and weight while the headlines keep their calligraphic voice. */
  --font-reading: var(--font-apparatus);
  --font-source: var(--font-apparatus);
  /* Provenance stays IBM Plex Mono in both languages: numerals are Western
     everywhere, so "a cited number is set in the mono" survives the script
     change instead of forking. */
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --neutral: #191F26;
    --surface: #0D1217;
    --on-surface: #EFE6DF;
    --border: #333A43;
    /* Not Powder Blue: --secondary is also the logic band and the dark
       rebind must keep its step against the Powder Blue knowledge band. */
    --secondary: #6E93BC;
    --tertiary-ink: #F08A66;
    --caution: #DEA372;
    --ground-0: #0D1217;
    --ground-1: #12181F;
    --ground-2: #191F26;
    --ground-invert: #191F26;
    --ground-invert-ink: #EFE6DF;
  }
  :root[data-palette='classic']:not([data-theme='light']) {
    --secondary: #A598B5;
    --tertiary-ink: #EA8280;
  }
}

:root[data-theme='dark'] {
  --neutral: #191F26;
  --surface: #0D1217;
  --on-surface: #EFE6DF;
  --border: #333A43;
  --secondary: #6E93BC; /* see the media block: keeps the logic band's step */
  --tertiary-ink: #F08A66;
  --caution: #DEA372;
  --ground-0: #0D1217;
  --ground-1: #12181F;
  --ground-2: #191F26;
  --ground-invert: #191F26;
  --ground-invert-ink: #EFE6DF;
}
:root[data-palette='classic'][data-theme='dark'] {
  --secondary: #A598B5;
  --tertiary-ink: #EA8280;
}

/* --- Reset ---------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* The depth meter and act bands are the wayfinding; nothing is scroll-jacked */
  scroll-padding-block-start: var(--xl);
}

body {
  margin: 0;
  background: var(--ground-0);
  color: var(--on-surface);
  font-family: var(--font-apparatus);
  font-size: calc(17px * var(--ar-size));
  line-height: calc(1.65 * var(--ar-lead));
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
  transition: background-color var(--t-state) linear, color var(--t-state) linear;
}

h1, h2, h3, h4, p, figure, blockquote { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
img, svg { max-width: 100%; }
table { border-collapse: collapse; width: 100%; }
button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }

:focus-visible {
  outline: 2px solid var(--tertiary-ink);
  outline-offset: 2px;
}

::selection { background: var(--selection-bg); color: #121C2A; }

/* The page is one continuous document, so the skip link jumps past the
   chrome rather than past a navigation tree. */
.skip-link {
  position: absolute;
  inset-inline-start: -9999px;
  inset-block-start: var(--md);
  z-index: 100;
  padding: var(--sm) var(--md);
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--on-surface);
  text-decoration: none;
}
.skip-link:focus { inset-inline-start: var(--md); }

/* --- Type levels ----------------------------------------------------------
   Display levels are ceilings on a clamp; body and every Plex Mono level are
   fixed px at every width, because reading size is a physiological constant
   and a source line that grows on a large monitor reads as a bug.          */

.display-xl, .display, .headline-lg, .headline-md, .pull-quote {
  font-family: var(--font-argument);
  font-weight: 600;
  font-feature-settings: 'tnum' 1;
  text-wrap: balance;
}

/* Display sizing is bound by viewport HEIGHT as well as width. The hero
   headline runs to four lines; at 128px on a 900px-tall laptop — the single
   most likely screen this is opened on — that pushed the lede and the scroll
   cue below the fold, so the first screen carried a headline and nothing
   else. The vh term keeps the opening argument whole on the screen it is
   actually read on, and the px ceiling still governs on tall displays. */
.display-xl {
  font-size: calc(clamp(48px, min(8.9vw, 9.5vh), 128px) * var(--ar-size) * var(--argument-scale));
  line-height: calc(0.92 * var(--ar-lead));
  letter-spacing: calc(-0.04em * var(--track));
  font-variation-settings: 'opsz' 72;
}
.display {
  font-size: calc(clamp(40px, min(6.1vw, 9vh), 88px) * var(--ar-size) * var(--argument-scale));
  line-height: calc(1 * var(--ar-lead));
  letter-spacing: calc(-0.03em * var(--track));
  font-variation-settings: 'opsz' 72;
}
.headline-lg {
  font-size: calc(clamp(34px, 3.75vw, 54px) * var(--ar-size) * var(--argument-scale));
  line-height: calc(1.08 * var(--ar-lead));
  letter-spacing: calc(-0.025em * var(--track));
  font-variation-settings: 'opsz' 60;
}
.headline-md {
  font-size: calc(clamp(26px, 2.5vw, 36px) * var(--ar-size) * var(--argument-scale));
  line-height: calc(1.2 * var(--ar-lead));
  letter-spacing: calc(-0.018em * var(--track));
  font-variation-settings: 'opsz' 36;
}
.pull-quote {
  font-weight: 400;
  font-size: calc(30px * var(--ar-size) * var(--argument-scale));
  line-height: calc(1.35 * var(--ar-lead));
  letter-spacing: calc(-0.01em * var(--track));
  font-variation-settings: 'opsz' 30;
}
:root[lang='ar'] .pull-quote {
  font-size: calc(30px * var(--ar-size) * var(--argument-scale) * 1.06);
  line-height: calc(1.42 * var(--ar-lead));
}

/* The quote block: the slide's core message compressed to a few words, set
   apart from the argument that earns it. A coral rule on the inline-start
   edge marks it as a voice, not a paragraph — the same ink the eyebrow
   wears, so the two "this matters" signals on a slide share one colour.
   --tertiary-ink is rebound by the dark theme and by [data-ground='invert'],
   so the rule and the italic survive the closing act. */
.quote-block {
  margin-block: var(--xl);
  padding-inline-start: var(--lg);
  border-inline-start: 3px solid var(--tertiary-ink);
}
.quote-block .pull-quote {
  font-style: italic;
  font-weight: 500;
}
/* Amiri's italic is a slanted latin habit, not an Arabic convention — the
   register shift is carried by the rule and the size alone. */
:root[lang='ar'] .quote-block .pull-quote { font-style: normal; }
.quote-block .quote-attr { margin-block-start: var(--md); }

/* Keyword emphasis inside prose (rich() in app.js). Weight alone, same ink:
   a second colour inside a paragraph would compete with the provenance
   chips, which own colour-as-signal on this page. */
.content strong.kw, .panel-body strong.kw { font-weight: 700; }
:root[lang='ar'] .content strong.kw, :root[lang='ar'] .panel-body strong.kw { font-weight: 700; }

/* The family switch at headline-sm is the visible seam between what the page
   claims (Newsreader / Amiri) and what it shows (Archivo / Plex Sans Arabic). */
.headline-sm {
  font-family: var(--font-apparatus);
  font-weight: 600;
  font-size: calc(22px * var(--ar-size));
  line-height: calc(1.3 * var(--ar-lead));
  letter-spacing: calc(-0.005em * var(--track));
}
.body-lg, .body-md, .body-sm { font-family: var(--font-reading); }
.body-lg { font-size: calc(20px * var(--ar-size) * var(--reading-scale)); line-height: calc(1.6 * var(--ar-lead)); }
.body-md { font-size: calc(17px * var(--ar-size) * var(--reading-scale)); line-height: calc(1.65 * var(--ar-lead)); }
.body-sm { font-size: calc(15px * var(--ar-size) * var(--reading-scale)); line-height: calc(1.55 * var(--ar-lead)); }

.label-caps {
  font-family: var(--font-apparatus);
  font-weight: 600;
  font-size: calc(11px * var(--ar-size) * var(--meta-scale) * var(--ui-scale));
  line-height: calc(1.3 * var(--ar-lead));
  letter-spacing: calc(0.14em * var(--track));
  text-transform: uppercase;
  font-feature-settings: 'case' 1;
}
/* Arabic has no case. The transform and the case feature do not apply, and
   those labels are distinguished by size, weight and colour alone. */
:root[lang='ar'] .label-caps { text-transform: none; font-feature-settings: normal; }

.data-lg, .data-md {
  font-family: var(--font-provenance);
  font-weight: 400;
  font-feature-settings: 'tnum' 1, 'zero' 1;
}
.source-sm, .source {
  font-family: var(--font-source);
  font-weight: 400;
  font-feature-settings: 'tnum' 1;
}
.data-lg { font-size: 19px; line-height: 1.4; }
.data-md { font-size: 14px; line-height: 1.45; }
.source-sm, .source { font-size: calc(12px * var(--meta-scale) * var(--source-scale)); line-height: 1.5; letter-spacing: 0.01em; }
.source { color: var(--secondary); }
:root[lang='ar'] .source-sm, :root[lang='ar'] .source { line-height: 1.6; letter-spacing: 0; font-weight: 500; }

.measure { max-inline-size: var(--measure-prose); }
.lede {
  font-family: var(--font-reading);
  margin-block-start: var(--lg);
}
.dek {
  font-family: var(--font-reading);
  color: var(--secondary);
  margin-block-start: var(--md);
}

/* Numerals are always Western and always in the mono, in both languages. */
.num, .figure-value, td.num, th.num { font-family: var(--font-provenance); font-feature-settings: 'tnum' 1, 'zero' 1; }

/* --- Canvas and grid ------------------------------------------------------
   Capped at 1440px and flush-start — not centred. Past the cap the page does
   not stretch; the extra width becomes trailing air on the inline-end side. */

.canvas {
  max-inline-size: var(--canvas);
  margin-inline: 0 auto;
  padding-inline: var(--margin);
}

.grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--gutter);
  align-items: start;
}

/* --- Sections -------------------------------------------------------------
   Sized by content. Nothing is locked to 100vh — that is where the dead space
   in the old paged build came from.                                        */

.section {
  /* Was --2xl (112px) each side, tuned for the long editorial read. With the
     short read as the default, 224px of air between neighbouring sections
     read as dead scroll, so the base step is --xl: 128px section-to-section
     inside an act, which still separates without stranding the reader. */
  padding-block: var(--xl);
  background: var(--ground-0);
  transition: background-color 240ms linear;
}
.section[data-ground='stock-1'] { background: var(--ground-1); }
.section[data-ground='stock-2'] { background: var(--ground-2); }
/* The one permitted inversion (DESIGN.md § Motion, the ground descent).
   Setting `color` alone is not enough and was a real contrast failure: every
   token referenced BY NAME — the rail heading, figure values, chips, rules —
   kept its light-mode value and went dark-on-dark. An inverted ground has to
   rebind the palette for its whole subtree, not just the inherited colour.
   These are the same values the dark theme uses, so the pairing is already
   contrast-checked. */
.section[data-ground='invert'] {
  --on-surface: #EFE6DF;
  --surface: #0D1217;
  --ground-0: #0D1217;
  --neutral: #191F26;
  --border: #333A43;
  --secondary: #98C1D9;
  --tertiary-ink: #F08A66;
  --caution: #DEA372;
  --primary: #8CA6C6;
  background: var(--ground-invert);
  color: var(--ground-invert-ink);
}
:root[data-palette='classic'] .section[data-ground='invert'] {
  --secondary: #A598B5;
  --tertiary-ink: #EA8280;
  --primary: #7D93AE;
}
/* An act that starts WITHOUT a band (the opening act draws none) needs its
   own larger threshold, --2xl, because there the padding is the only
   separation there is. */
.section[data-act-start='true'] { padding-block-start: var(--2xl); }
/* An act band is itself the separator, and it already carries --xl of its
   own padding on both sides — stacking a full act threshold under it put
   the act name and the headline it announces ~240px apart with nothing
   between them. --lg is enough to lift the headline off the band. */
.act-band + .section[data-act-start='true'] { padding-block-start: var(--lg); }
/* The opening sets its own first-screen budget a few rules down, but the
   attribute selector above outranks a lone class and was silently overriding
   it — the one section that must fit a screen was being given the largest
   top padding on the page. Restated here at matching specificity. */
.section--opening[data-act-start='true'] { padding-block-start: var(--xl); }

.content { grid-column: 1 / span 8; min-inline-size: 0; }
.content--wide { grid-column: 1 / -1; }
/* With the rails hidden deck-wide, every section is content--wide and an SVG
   figure would scale to ~1.5x the size its type was drawn for (the viewBox
   keeps its aspect, so width drives height). Cap the drawing near its
   rail-era width; panel grids and process steps keep the full span, which is
   the width they were always designed to use. */
.content--wide .figure { max-inline-size: 780px; }

/* The eyebrow is the section's nameplate, and at 11px in the muted secondary
   it read as fine print above the headline rather than as the marker that
   names the beat. Three changes carry the weight without letting it compete
   with the headline itself: the coral ink in place of the muted purple, a
   heavier cut one step up in size, and a short accent rule anchoring the
   label to the column edge.

   --tertiary-ink is used rather than --tertiary because it is rebound by the
   dark theme AND by [data-ground='invert'], so the eyebrow stays legible on
   the inverted closing act in either theme. The rule is `currentColor`, so it
   follows the text through every one of those rebindings.

   align-items is center rather than baseline: a baseline row would hang the
   3px rule off the text baseline instead of centring it on the cap height. */
.eyebrow {
  display: flex;
  align-items: center;
  gap: var(--sm);
  color: var(--tertiary-ink);
  font-weight: 700;
  font-size: calc(12px * var(--ar-size) * var(--meta-scale) * var(--ui-scale));
  margin-block-end: var(--md);
}
.eyebrow::before {
  content: '';
  flex: none;
  inline-size: 20px;
  block-size: 3px;
  background: currentColor;
}

.rule-section {
  block-size: 2px;
  border: 0;
  background: currentColor;
  margin-block: var(--lg) var(--xl);
  opacity: 0.9;
}
.rule {
  block-size: 1px;
  border: 0;
  background: var(--border);
  margin-block: var(--lg);
}

.content > p + p { margin-block-start: var(--md); }
.content > .body-md, .content > .body-lg { margin-block-start: var(--lg); }

/* --- Act bands ------------------------------------------------------------
   The one element permitted to break the canvas. Steps down the depth ramp:
   bronze at the top of the page, dusk at the bottom.                       */

.act-band {
  padding-block: var(--xl);
  color: var(--surface);
  overflow: hidden;
}
.act-band .canvas { display: flex; align-items: baseline; gap: var(--lg); }
.act-band[data-depth='1'] { background: var(--stratum-top); color: var(--on-surface); }
.act-band[data-depth='2'] { background: var(--tertiary); color: var(--on-surface); }
.act-band[data-depth='3'] { background: var(--stratum-mid); color: var(--band-mid-ink); }
.act-band[data-depth='4'] { background: var(--secondary); color: #FBF6F1; }
.act-band[data-depth='5'] { background: var(--primary); color: #FBF6F1; }
.act-band[data-depth='6'] { background: var(--on-surface); color: var(--surface); }
.act-name { font-size: calc(11px * var(--ar-size) * var(--meta-scale) * var(--ui-scale)); }
.act-index { font-family: var(--font-provenance); font-size: 12px; opacity: 0.8; }

/* --- Provenance rail ------------------------------------------------------
   Sticky, and sized to its content. Empty vertical space here is not neutral:
   it reads as an unsourced claim, so the layout must never produce it.     */

.rail {
  grid-column: 9 / span 4;
  position: sticky;
  inset-block-start: var(--xl);
  min-inline-size: 0;
  border-inline-start: 1px solid var(--border);
  padding-inline-start: var(--lg);
  color: var(--secondary);
}
.rail h3 {
  font-family: var(--font-apparatus);
  font-weight: 600;
  font-size: calc(11px * var(--ar-size) * var(--meta-scale) * var(--ui-scale));
  letter-spacing: calc(0.14em * var(--track));
  text-transform: uppercase;
  color: var(--on-surface);
  margin-block-end: var(--md);
}
:root[lang='ar'] .rail h3 { text-transform: none; }
.rail p + p { margin-block-start: var(--md); }
:root[lang='ar'] .rail p + p { margin-block-start: 12px; }
:root[lang='ar'] .rail { padding-inline-start: 28px; }
.rail .source a { display: inline-flex; vertical-align: -0.115em; margin-inline-start: 4px; }
.rail-chip-block { margin-block-end: var(--md); }
.rail-chip-block p { margin-block-start: var(--sm); }
.rail-empty { color: var(--caution); }

/* --- Chips ---------------------------------------------------------------- */

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--xs);
  padding: var(--xs) var(--sm);
  background: var(--neutral);
  font-family: var(--font-apparatus);
  font-weight: 600;
  font-size: calc(11px * var(--ar-size) * var(--meta-scale) * var(--ui-scale));
  letter-spacing: calc(0.14em * var(--track));
  text-transform: uppercase;
}
:root[lang='ar'] .chip { text-transform: none; }
.chip--est { color: var(--tertiary-ink); }
.chip--flag { color: var(--caution); }
.chip-note { letter-spacing: calc(0.06em * var(--track)); opacity: 0.85; }

/* --- Links, buttons ------------------------------------------------------- */

a { color: var(--tertiary-ink); text-decoration: underline; text-underline-offset: 2px; text-decoration-thickness: 1px; }
a:hover { text-decoration-thickness: 2px; }

.button-secondary,
.icon-button {
  display: inline-flex;
  align-items: center;
  gap: var(--sm);
  padding: var(--sm) var(--md);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--primary);
  font-family: var(--font-apparatus);
  font-weight: 600;
  font-size: calc(11px * var(--ar-size) * var(--meta-scale) * var(--ui-scale));
  letter-spacing: calc(0.14em * var(--track));
  text-transform: uppercase;
  transition: color var(--t-state) linear, background-color var(--t-state) linear, border-color var(--t-state) linear;
}
:root[lang='ar'] .button-secondary, :root[lang='ar'] .icon-button { text-transform: none; }

.cta {
  display: inline-flex;
  align-items: center;
  padding: var(--md) var(--lg);
  margin-block-start: var(--xl);
  background: var(--tertiary-ink);
  color: var(--surface);
  text-decoration: none;
  font-family: var(--font-apparatus);
  font-weight: 600;
  font-size: calc(11px * var(--ar-size) * var(--meta-scale) * var(--ui-scale));
  letter-spacing: calc(0.14em * var(--track));
  text-transform: uppercase;
  transition: background-color var(--t-state) linear;
}
:root[lang='ar'] .cta { text-transform: none; }

/* --- Tables ---------------------------------------------------------------
   Numeric columns align inline-end, name columns inline-start. Stated
   logically because it must survive the mirror.                            */

.table-scroll { overflow-x: auto; margin-block-start: var(--lg); }
table.evidence { font-family: var(--font-provenance); font-size: 14px; line-height: 1.45; }
table.evidence caption {
  text-align: start;
  font-family: var(--font-apparatus);
  font-weight: 600;
  font-size: calc(11px * var(--ar-size) * var(--meta-scale) * var(--ui-scale));
  letter-spacing: calc(0.14em * var(--track));
  text-transform: uppercase;
  color: var(--secondary);
  padding-block-end: var(--md);
}
:root[lang='ar'] table.evidence caption { text-transform: none; }
table.evidence th {
  text-align: start;
  font-family: var(--font-apparatus);
  font-weight: 600;
  font-size: calc(11px * var(--ar-size) * var(--meta-scale) * var(--ui-scale));
  letter-spacing: calc(0.14em * var(--track));
  text-transform: uppercase;
  color: var(--secondary);
  padding: var(--sm);
  border-block-end: 1px solid var(--border);
  white-space: nowrap;
}
:root[lang='ar'] table.evidence th { text-transform: none; }
table.evidence td {
  padding: var(--sm);
  border-block-end: 1px solid var(--border);
  vertical-align: baseline;
}
table.evidence td.name, table.evidence th.name { font-family: var(--font-apparatus); text-align: start; }
table.evidence td.num, table.evidence th.num { text-align: end; white-space: nowrap; }
table.evidence tr:last-child td { border-block-end: 0; }

/* --- Icons ---------------------------------------------------------------- */

.ph { flex: none; vertical-align: -0.115em; }

/* --- Template: opening ---------------------------------------------------- */

/* Top-aligned, not vertically centred. Centring a five-line display headline
   in 88vh pushed the dek down onto the pinned scroll cue, and it also let the
   opening end exactly at the fold — which reads as the page finishing rather
   than continuing. The claim sits vertically centred in its screen — with
   the short read's two lines, top-aligned it left a hole under the lede
   that read as a gap rather than as an opening. The act band that follows
   still peeks above the fold, which is the honest signal that there is
   more; the mobile breakpoint resets to block flow where centring in a
   min-height would be meaningless. */
.section--opening {
  padding-block-start: var(--xl);
  min-block-size: 72vh;
  display: grid;
  align-content: center;
}
/* The opening is the one section that must fit a screen, so its rule sits
   tighter than the standard 32/64. Everywhere else the section rule keeps the
   full breathing room — this is a first-screen budget, not a new default. */
.section--opening .rule-section { margin-block: var(--md) var(--lg); }
.section--opening .scroll-cue { margin-block-start: var(--lg); }
/* In flow, directly under the dek. Pinning it to the viewport bottom kept it
   visible but dropped it on top of the dek at 1440x900 — and a cue that
   collides with the copy it is inviting you past is worse than one a little
   low. Top-aligning the opening (above) is what buys it room on the fold.
   motion.js removes it permanently on the first scroll. */
.scroll-cue {
  margin-block-start: var(--xl);
  display: inline-flex;
  align-items: center;
  gap: var(--sm);
  color: var(--secondary);
  font-family: var(--font-provenance);
  font-size: calc(12px * var(--meta-scale) * var(--source-scale));
  transition: opacity var(--t-fade) var(--ease-enter);
}
.scroll-cue[hidden] { display: none; }

/* --- Template: panels ----------------------------------------------------- */

.panels {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--md);
  margin-block-start: var(--xl);
}
.panel { background: var(--neutral); padding: var(--lg); }
/* The six data-source cards carry tag rows, so they need more width than the
   generic 260px minimum gives them: at 360px they settle into two rows of
   three at full content width, and still collapse to one column on narrow
   viewports through the same auto-fit mechanism.

   The three use-case cards take the same minimum for the opposite reason —
   each carries a long body plus its source tags, and the generic minimum
   would let a fourth phantom column form on very wide screens and leave the
   row visibly short. */
#sources .panels,
#usecase .panels { grid-template-columns: repeat(auto-fit, minmax(360px, 1fr)); }
.panel-icon { color: var(--secondary); margin-block-end: var(--md); }
.panel-icon svg { display: block; }
.panel-tags { display: flex; flex-wrap: wrap; gap: var(--xs); margin-block-start: var(--md); }
.panel-tag {
  border: 1px solid var(--border);
  padding: 4px 8px;
  font-family: var(--font-apparatus); font-weight: 600;
  font-size: calc(10px * var(--ar-size) * var(--meta-scale) * var(--ui-scale));
  letter-spacing: calc(0.1em * var(--track));
  text-transform: uppercase; color: var(--secondary);
}
:root[lang='ar'] .panel-tag { text-transform: none; }
.panel-label {
  font-family: var(--font-apparatus); font-weight: 600;
  font-size: calc(11px * var(--ar-size) * var(--meta-scale) * var(--ui-scale));
  letter-spacing: calc(0.14em * var(--track));
  text-transform: uppercase; color: var(--secondary);
}
:root[lang='ar'] .panel-label { text-transform: none; }
.panel-value {
  font-family: var(--font-argument); font-weight: 600;
  font-size: calc(22px * var(--ar-size) * var(--argument-scale));
  line-height: calc(1.25 * var(--ar-lead));
  margin-block: var(--sm);
}
.panel-body {
  font-family: var(--font-reading);
  font-size: calc(15px * var(--ar-size) * var(--reading-scale));
  line-height: calc(1.55 * var(--ar-lead));
}
/* The two founder cards are the only panels whose body is a paragraph rather
   than a caption, and they are the only ones that sit two-across instead of
   three or four — so they are twice as wide with the smallest type on them.
   At body-sm the bios ran 75 characters to the line, past the normative 66
   (DESIGN.md § Grid), on the one ground where halation is worst.

   Stepping them to body-md answers both at once rather than inventing a
   value: 17px in the same 576px card is ~66 characters, so the measure comes
   back inside the rule by getting BIGGER, not narrower, and the extra leading
   carries a three-sentence block that the caption size was never sized for.
   The measure cap stays as a guard in case the card ever widens. */
#team .panel-body {
  font-size: calc(17px * var(--ar-size) * var(--reading-scale));
  line-height: calc(1.65 * var(--ar-lead));
  max-inline-size: var(--measure-prose);
}
/* 8px between a 22px name and a 15px caption reads as one block; between a
   name and a paragraph it reads as a collision. */
#team .panel-value { margin-block-end: var(--md); }
.panel-photo {
  display: block;
  inline-size: 88px; block-size: 88px;
  object-fit: cover;
  border-radius: 50%;
  margin-block-end: var(--md);
}
/* Institution marks (the early-demand panels). The assets are pre-baked to
   greyscale ink on a white plate (tools cannot be trusted to re-run, so the
   bake lives in the files, not in a filter chain); the blend then makes the
   plate invisible on any ground. A full-colour logo row would be the only
   element on the page with its own palette, which is why none is allowed.
   Dark themes invert the same asset so the mark reads as paper on ink. */
.panel-logo {
  display: block;
  block-size: 80px;
  inline-size: auto;
  max-inline-size: 100%;
  object-fit: contain;
  margin-block-end: var(--md);
  mix-blend-mode: multiply;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .panel-logo { filter: invert(1); mix-blend-mode: screen; }
}
:root[data-theme='dark'] .panel-logo { filter: invert(1); mix-blend-mode: screen; }
.panel-link {
  display: inline-block;
  margin-block-start: var(--md);
  font-family: var(--font-apparatus);
  font-weight: 600;
  font-size: calc(11px * var(--ar-size) * var(--meta-scale) * var(--ui-scale));
  letter-spacing: calc(0.14em * var(--track));
  text-transform: uppercase;
  color: var(--tertiary-ink);
}
:root[lang='ar'] .panel-link { text-transform: none; }

/* --- Template: ledger / ladder -------------------------------------------- */

.ledger { margin-block-start: var(--xl); }
.ledger-row {
  display: grid;
  grid-template-columns: 3.5rem minmax(0, 14rem) minmax(0, 1fr);
  gap: var(--md);
  align-items: baseline;
  padding-block: var(--md);
  border-block-start: 1px solid var(--border);
}
.ledger-row:last-child { border-block-end: 1px solid var(--border); }
.ledger-n { font-family: var(--font-provenance); font-size: 14px; color: var(--secondary); }
.ledger-label {
  font-family: var(--font-apparatus);
  font-weight: 600;
  font-size: calc(17px * var(--ar-size) * var(--ui-scale));
}
.ledger-body {
  font-family: var(--font-reading);
  font-size: calc(15px * var(--ar-size) * var(--reading-scale));
  color: var(--secondary);
}

/* --- Template: process ---------------------------------------------------- */

.process { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--lg); margin-block-start: var(--xl); }
.process-step { border-block-start: 2px solid var(--on-surface); padding-block-start: var(--md); }
.process-n { font-family: var(--font-provenance); font-size: 14px; color: var(--secondary); }
.process-label {
  font-family: var(--font-argument);
  font-weight: 600;
  font-size: calc(22px * var(--ar-size) * var(--argument-scale));
  margin-block: var(--sm);
}
.process-body {
  font-family: var(--font-reading);
  font-size: calc(15px * var(--ar-size) * var(--reading-scale));
  color: var(--secondary);
}

/* --- Template: lead figure ------------------------------------------------ */

.figure-lead { margin-block-start: var(--xl); }
.figure-lead .figure-value {
  font-family: var(--font-argument);
  font-weight: 600;
  font-size: calc(clamp(56px, min(8.9vw, 13vh), 128px) * var(--ar-size) * var(--argument-scale));
  line-height: calc(0.92 * var(--ar-lead));
  letter-spacing: calc(-0.04em * var(--track));
  font-variation-settings: 'opsz' 72;
  font-feature-settings: 'tnum' 1;
}
.figure-lead .qualifier {
  font-family: var(--font-reading);
  margin-block-start: var(--lg);
  font-size: calc(20px * var(--ar-size) * var(--reading-scale));
  max-inline-size: 52ch;
}
.figure-lead .chip { margin-block-start: var(--md); }

/* --- Figures --------------------------------------------------------------
   Typographic figures (ledger, ladder, process, strata) are final. The SVG
   figures land in Phase 7; until then each renders its own dataset as a
   table — which is the accessible fallback those figures need anyway, so the
   content is complete rather than stubbed. */

.figure { margin-block-start: var(--xl); margin-inline: 0; }
.figure--table-only > .source { margin-block-start: var(--md); }

/* Charts stay dir="ltr" in both languages. Quantitative axes read
   left-to-right even in Arabic financial publishing, our numerals are
   Western, and a mirrored time series reads backwards to exactly the audience
   the Arabic build exists for. Only the text nodes flip: unicode-bidi:
   plaintext resolves each text node's own base direction from its first
   strong character, so Arabic prose (and Arabic mixed with an embedded
   Latin name or number) reads RTL while the geometry stays put. */
.fig { margin: 0; }
.fig-svg text { unicode-bidi: plaintext; }
.fig-plot { overflow-x: auto; overflow-y: hidden; }
.fig-svg { display: block; width: 100%; min-width: 640px; height: auto; }
.fig-cap { margin-block-start: var(--md); max-inline-size: 66ch; }
.fig-table { margin-block-start: var(--md); }
/* Set in the apparatus rather than the reading face: this is a control, and
   in the reading face at body size it read as a stray sentence that happened
   to carry a triangle. Caps and the apparatus weight mark it as chrome. */
.fig-table > summary {
  cursor: pointer;
  padding-block: var(--xs);
  color: var(--secondary);
  font-family: var(--font-apparatus);
  font-weight: 600;
  font-size: calc(11px * var(--ar-size) * var(--meta-scale) * var(--ui-scale));
  letter-spacing: calc(0.12em * var(--track));
  text-transform: uppercase;
}
/* Arabic has no case — same carve-out the other apparatus labels take. */
:root[lang='ar'] .fig-table > summary { text-transform: none; letter-spacing: normal; }
.fig-table > summary:hover { color: var(--on-surface); }
.fig-table table.evidence { margin-block-start: var(--sm); }

/* Text in a figure wears TEXT tokens, never a series colour: a mark carries
   identity, a label never does. */
.fig-t { font-family: var(--font-provenance); fill: var(--secondary); }
.fig-tick { font-size: 11px; }
.fig-label { font-size: 12px; fill: var(--on-surface); }
.fig-value { font-size: 13px; fill: var(--on-surface); font-feature-settings: 'tnum' 1, 'zero' 1; }
.fig-big { font-size: 22px; }
.fig-kicker {
  font-family: var(--font-apparatus); font-size: 11px; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase; fill: var(--secondary);
}
:root[lang='ar'] .fig-kicker { text-transform: none; letter-spacing: 0; }
.fig-ours-t { fill: var(--tertiary-ink); }
.fig-inbar { font-family: var(--font-provenance); font-size: 13px; fill: #FBF6F1; }
.fig-inbar--dim { opacity: 0.75; font-size: 11px; }

/* Axes and grid: hairline, solid, recessive. Never dashed. */
.fig-axis-line { stroke: var(--border); stroke-width: 1; }
.fig-grid-line { stroke: var(--border); stroke-width: 1; opacity: 0.4; }
.fig-rule { stroke: var(--border); stroke-width: 1; }
.fig-connector { stroke: var(--border); stroke-width: 1; }
.fig-bracket { stroke: var(--on-surface); stroke-width: 1; }
.fig-marker-line { stroke: var(--tertiary-ink); stroke-width: 2; }
.fig-slope { stroke: var(--primary); stroke-width: 2; stroke-linecap: round; fill: none; }

/* Marks. Cut ends everywhere — radius is zero in this system, so the dataviz
   4px rounded data-end is overridden. */
.fig-mark { shape-rendering: crispEdges; }
.fill-substrate { fill: var(--primary); }
.fill-logic { fill: var(--secondary); }
.fill-knowledge { fill: var(--stratum-mid); }
.fill-signal { fill: var(--tertiary); }
.fill-tip { fill: var(--stratum-top); }
.fill-tam { fill: var(--stratum-top); }
.fill-sam { fill: var(--stratum-mid); }
.fill-som { fill: var(--tertiary-ink); }
.fig-bar--reported { fill: none; stroke: var(--primary); stroke-width: 1.5; }
.fig-bar--undisclosed { fill: var(--border); }

.fig-dot-ring { fill: var(--ground-0); }
.fig-dot { fill: var(--on-surface); }
.fig-ink { fill: var(--on-surface); }
.fig-muted { fill: var(--secondary); }
.fig-ours { fill: var(--tertiary-ink); }
.fig-us-cross { stroke: var(--tertiary-ink); stroke-width: 2; }
.fig-node { fill: var(--primary); fill-opacity: 0.18; stroke: var(--primary); stroke-width: 1.5; }
.fig-node--public { fill-opacity: 0; }
.fig-node--est { stroke-dasharray: 3 2; }

/* Texture is the evidence channel: a 45° hatch means EST or FLAG. It survives
   greyscale and colour-vision deficiency without depending on hue, which the
   depth ramp cannot do (PLAN.md §3). */
.fig-hatch-bg { fill: var(--ground-0); }
.fig-hatch-line { stroke: var(--primary); stroke-width: 3; }
.fig-est { }
.fig-flag { }

.fig-hit { cursor: default; outline-offset: 3px; }
.fig-hit:focus-visible { outline: 2px solid var(--tertiary-ink); }

.fig-tip {
  position: absolute;
  z-index: 70;
  transform: translate(-50%, -100%);
  max-inline-size: 30ch;
  padding: var(--sm) var(--md);
  background: var(--on-surface);
  color: var(--surface);
  font-family: var(--font-provenance);
  font-size: 12px;
  line-height: 1.45;
  pointer-events: none;
}
.fig-tip[hidden] { display: none; }

/* --- Template: strata -----------------------------------------------------
   The set piece. It should read as a built stack, not as five unrelated
   stripes, so the layers taper toward the top and the waterline stays bound
   to the structure rather than to the page width.                          */

.strata {
  margin-block-start: var(--xl);
  display: flex;
  flex-direction: column;
  gap: 2px;
  /* Capped rather than filling the wide (rail-less) content column: at full
     12-column width each band stretches into a flat ribbon and the taper
     that makes this read as a pyramid disappears. */
  max-inline-size: 860px;
  margin-inline: 0 auto;
}
.band {
  position: relative;
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  align-items: center;
  gap: clamp(var(--sm), 1.5vw, var(--lg));
  padding-block: var(--md);
  padding-inline: clamp(var(--md), 2vw, var(--xl));
  min-block-size: 56px;
  inline-size: min(100%, var(--band-span, 100%));
  margin-inline: auto;
  clip-path: polygon(var(--band-shoulder, 0%) 0,
                     calc(100% - var(--band-shoulder, 0%)) 0,
                     100% 100%,
                     0 100%);
}
.band--tip {
  --band-span: 58%;
  --band-shoulder: 7%;
  background: var(--stratum-top);
  color: #121C2A;
}
.band--signal {
  --band-span: 68%;
  --band-shoulder: 5.5%;
  background: var(--tertiary);
  color: #121C2A;
}
.band--knowledge {
  --band-span: 80%;
  --band-shoulder: 4%;
  background: var(--stratum-mid);
}
.band--logic {
  --band-span: 90%;
  --band-shoulder: 2.25%;
  background: var(--secondary);
  color: #FBF6F1;
}
.band--substrate {
  --band-span: 100%;
  --band-shoulder: 0%;
  background: var(--primary);
  color: #FBF6F1;
}
.band-label {
  font-family: var(--font-apparatus); font-weight: 600;
  font-size: calc(11px * var(--ar-size) * var(--meta-scale) * var(--ui-scale));
  letter-spacing: calc(0.14em * var(--track));
  text-transform: uppercase; white-space: nowrap;
}
:root[lang='ar'] .band-label { text-transform: none; }
.band-note {
  font-family: var(--font-reading);
  font-size: calc(15px * var(--ar-size) * var(--reading-scale));
  justify-self: end;
  text-align: start;
}

/* Rosewood carries no text — it fails AA against both paper and ink. Its
   label sits outside, joined by a leader rule so it cannot read as an
   orphaned caption under a stray coloured bar. */
.band-caption {
  display: flex;
  align-items: center;
  gap: var(--md);
  inline-size: min(100%, 80%);
  margin-inline: auto;
  padding-block: var(--sm);
  padding-inline: clamp(var(--md), 2vw, var(--xl));
}
.band-leader { flex: none; inline-size: var(--lg); block-size: 1px; background: var(--border); }
.outside-label {
  font-family: var(--font-apparatus); font-weight: 600;
  font-size: calc(11px * var(--ar-size) * var(--meta-scale) * var(--ui-scale));
  letter-spacing: calc(0.14em * var(--track));
  text-transform: uppercase; white-space: nowrap;
}
:root[lang='ar'] .outside-label { text-transform: none; }
.band-caption-note {
  font-family: var(--font-reading);
  font-size: calc(15px * var(--ar-size) * var(--reading-scale));
  color: var(--secondary);
}

/* The waterline. The one documented exception to one 2px section rule per
   section — the same mark declaring a division, one level down. */
.waterline {
  display: flex;
  align-items: center;
  gap: var(--md);
  inline-size: min(100%, 72%);
  margin-block: var(--sm);
  margin-inline: auto;
}
.waterline-rule { flex: 1; block-size: 2px; background: currentColor; }
.waterline-label {
  font-family: var(--font-apparatus); font-weight: 600;
  font-size: calc(11px * var(--ar-size) * var(--meta-scale) * var(--ui-scale));
  letter-spacing: calc(0.14em * var(--track));
  text-transform: uppercase; color: var(--secondary); white-space: nowrap;
}
:root[lang='ar'] .waterline-label { text-transform: none; }

@media (max-width: 900px) {
  .band--tip { --band-span: 76%; --band-shoulder: 5%; }
  .band--signal { --band-span: 84%; --band-shoulder: 4%; }
  .band--knowledge { --band-span: 90%; --band-shoulder: 3%; }
  .band--logic { --band-span: 95%; --band-shoulder: 1.5%; }
  .waterline { inline-size: min(100%, 84%); }
  .band-note, .band-caption-note { font-size: calc(14px * var(--ar-size)); }
}

/* --- Depth meter ----------------------------------------------------------
   The page's only persistent navigation, and the strata in miniature. It
   never regains a numeric "9 / 13" reading — counting frames is the metaphor
   this rebuild removed.                                                     */

.depth-meter {
  position: fixed;
  inset-inline-start: var(--md);
  inset-block-start: 50%;
  transform: translateY(-50%);
  z-index: 40;
  display: flex;
  align-items: center;
  gap: var(--md);
}
.depth-stack { display: flex; flex-direction: column; gap: 2px; }
.depth-seg { inline-size: 6px; block-size: 26px; opacity: 0.25; transition: opacity var(--t-state) linear; }
.depth-seg[data-filled='true'] { opacity: 1; }
.depth-seg--tip { background: var(--stratum-top); }
.depth-seg--signal { background: var(--tertiary); }
.depth-seg--knowledge { background: var(--stratum-mid); }
.depth-seg--logic { background: var(--secondary); }
.depth-seg--substrate { background: var(--primary); }
.depth-act {
  font-family: var(--font-apparatus); font-weight: 600;
  font-size: calc(11px * var(--ar-size) * var(--meta-scale) * var(--ui-scale));
  letter-spacing: calc(0.14em * var(--track));
  text-transform: uppercase;
  color: var(--secondary);
  writing-mode: vertical-rl;
  transition: opacity var(--t-fade) var(--ease-enter);
}
:root[lang='ar'] .body-lg,
:root[lang='ar'] .body-md,
:root[lang='ar'] .body-sm,
:root[lang='ar'] .lede,
:root[lang='ar'] .dek,
:root[lang='ar'] .panel-body,
:root[lang='ar'] .ledger-body,
:root[lang='ar'] .process-body,
:root[lang='ar'] .figure-lead .qualifier,
:root[lang='ar'] .band-note,
:root[lang='ar'] .band-caption-note {
  font-weight: 500;
}
:root[lang='ar'] .depth-act { text-transform: none; }
:root[dir='rtl'] .depth-act { writing-mode: vertical-lr; }

/* --- Chrome --------------------------------------------------------------- */

.chrome {
  position: fixed;
  inset-block-end: 0;
  inset-inline: 0;
  z-index: 50;
  display: flex;
  /* Four controls now (mode, language, appendix, theme) — wrap to a second
     row on narrow viewports rather than pushing the row off-canvas. */
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--sm);
  padding: var(--md);
  pointer-events: none;
}
.chrome > * { pointer-events: auto; }

/* --- Drawer ---------------------------------------------------------------
   Enters from the inline-end edge, the same side as its control. */

.drawer {
  position: fixed;
  inset-block: 0;
  inset-inline-end: 0;
  z-index: 60;
  inline-size: min(560px, 100vw);
  background: var(--surface);
  border-inline-start: 1px solid var(--border);
  padding: var(--xl) var(--lg);
  overflow-y: auto;
  transform: translateX(calc(100% * var(--dir)));
  visibility: hidden;
  transition: transform var(--t-fade) var(--ease-drawer), visibility 0s linear var(--t-fade);
}
.drawer[data-open='true'] {
  transform: translateX(0);
  visibility: visible;
  transition: transform 260ms var(--ease-drawer), visibility 0s linear 0s;
}
.drawer-head { display: flex; justify-content: space-between; align-items: center; gap: var(--md); margin-block-end: var(--lg); }
.drawer h3 { margin-block-end: var(--md); }
.drawer .source { margin-block-end: var(--sm); }

/* ==========================================================================
   MOTION
   Rules and fields move. Words and numbers do not.
   Nothing in IBM Plex Mono ever animates.
   ========================================================================== */

/* 2 · Rule setting — the signature. Draws from the flush-start margin.
   A rule is pure apparatus: it carries no information the reader is waiting
   for, so animating it costs nothing. */
/* ---- THE MOTION SYSTEM FAILS OPEN ----------------------------------------
   Every hidden-until-revealed state below is scoped to [data-motion='on'],
   set before first paint by the inline script in index.html and REMOVED by
   motion.js if it throws. So: no JavaScript → fully visible. JavaScript but
   motion.js errors → fully visible. Only with the motion system confirmed
   running does anything start hidden.

   This is not defensive padding. These states default to invisible, so
   without the scope a single uncaught error anywhere in motion.js renders the
   whole page as empty space — for a document whose entire job is to be opened
   once, from a link, by someone looking for a reason to stop reading. */

.rule-section, .rule, .band-leader {
  transform-origin: var(--origin-start);
  transition: transform var(--t-rule) var(--ease-move);
}
[data-motion='on'] .rule-section,
[data-motion='on'] .rule,
[data-motion='on'] .band-leader { transform: scaleX(0); }
[data-motion='on'] [data-set='true'] .rule-section,
[data-motion='on'] [data-set='true'] .rule,
[data-motion='on'] [data-set='true'] .band-leader { transform: scaleX(1); }

/* The process step's top rule is a border, not an element, so it sets by
   colour rather than by scale. Same duration, same curve. */
.process-step { transition: border-block-start-color var(--t-rule) var(--ease-move); }
[data-motion='on'] .process-step { border-block-start-color: transparent; }
[data-motion='on'] [data-set='true'] .process-step { border-block-start-color: currentColor; }

/* Rules within one section stagger, capped at 180ms total. */
[data-set='true'] .rule:nth-of-type(2) { transition-delay: var(--stagger); }
[data-set='true'] .rule:nth-of-type(3) { transition-delay: calc(var(--stagger) * 2); }
[data-set='true'] .rule:nth-of-type(4) { transition-delay: calc(var(--stagger) * 3); }

/* 3 · The press pass — ink meeting paper. Never partial opacity, never
   blurred, never translated: not yet printed, or fully printed. */
.press { transition: clip-path var(--t-press) var(--ease-press); }
[data-motion='on'] .press { clip-path: var(--clip-hidden); }
[data-motion='on'] [data-set='true'] .press { clip-path: var(--clip-shown); }

/* 4 · The strata build — scroll-linked and reversible. It is a diagram being
   read, not an animation being watched. */
/* The waterline scrubs WITH the build rather than firing with the section's
   other rules. DESIGN.md has it drawing last, after every band has settled —
   driven by the section's `data-set` it drew first, alongside the headline
   rule, which inverts the sequence the diagram depends on. `--band-p` is set
   on the .waterline wrapper and inherits down to the rule. */
.band, .band-caption, .waterline-rule { transform-origin: var(--origin-start); }
[data-motion='on'] .band,
[data-motion='on'] .band-caption,
[data-motion='on'] .waterline-rule { transform: scaleX(var(--band-p, 0)); }
[data-motion='on'] .strata[data-mode='instant'] .band,
[data-motion='on'] .strata[data-mode='instant'] .band-caption {
  transform: scaleX(1);
  transition: transform var(--t-band) var(--ease-move);
}
.band-label, .band-note, .outside-label, .band-caption-note {
  transition: opacity var(--t-fade) var(--ease-enter);
}
[data-motion='on'] .band-label,
[data-motion='on'] .band-note,
[data-motion='on'] .outside-label,
[data-motion='on'] .band-caption-note { opacity: var(--band-label-o, 0); }

/* 5 · Act thresholds — the field extends from the inline-start edge. */
.act-band { transition: clip-path var(--t-press) var(--ease-press); }
[data-motion='on'] .act-band { clip-path: var(--clip-hidden); }
[data-motion='on'] .act-band[data-set='true'] { clip-path: var(--clip-shown); }

/* 7 · Provenance crossfade — opacity only. The rail never moves, and the
   citations never translate or stagger: they are Plex Mono. */
.rail { transition: opacity var(--t-fade) var(--ease-enter); }

/* 8 · State feedback — colour only. No lift, no scale, no shadow. */
@media (hover: hover) and (pointer: fine) {
  .button-secondary:hover, .icon-button:hover { background: var(--neutral); color: var(--on-surface); }
  .cta:hover { background: var(--on-surface); }
  .depth-seg:hover { opacity: 1; }
}

/* Theme switching suppresses every transition for one frame, or the whole
   page smears through 120ms of intermediate colour. */
:root[data-theme-switching] *,
:root[data-theme-switching] *::before,
:root[data-theme-switching] *::after {
  transition: none !important;
  animation: none !important;
}

/* --- Reduced motion -------------------------------------------------------
   Removes durations AND delays, and resolves every animation to its FINAL
   state — printed, drawn, built, filled. Nothing is left half-revealed and
   nothing is hidden.                                                       */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-delay: 0s !important;
    transition-duration: 0.001ms !important;
    transition-delay: 0s !important;
  }
  .rule-section, .rule, .band-leader, .waterline-rule { transform: scaleX(1) !important; }
  .press, .act-band { clip-path: inset(0 0 0 0) !important; }
  .band, .band-caption { transform: scaleX(1) !important; }
  .band-label, .band-note, .outside-label, .band-caption-note { opacity: 1 !important; }
  .process-step { border-block-start-color: currentColor !important; }
}

/* --- Responsive -----------------------------------------------------------
   Below 900px the provenance rail moves beneath its content, separated by a
   hairline, still in source-sm. It collapses in position, never in presence. */

@media (max-width: 1100px) {
  :root { --margin: 48px; }
  .depth-meter { display: none; }

  /* THE FIGURES WERE BEING SHRUNK, NOT FITTED.
     A 640px floor against a 720-unit viewBox scaled every label DOWN by
     0.889 — an 11px tick landed at 9.8px, below any legible floor — and the
     chart still did not fit the column. It cannot fit: these figures spend up
     to 300 of their 720 units on a label gutter, so shrinking further only
     trades a clipped chart for an illegible one. The scroller is the honest
     answer; the floor is what was wrong.

     Raise the floor ABOVE the authored width rather than enlarging the text
     inside it. Every label, tick and value gains the same 11% and no geometry
     moves, so nothing can newly collide or escape the viewBox — which
     enlarging the type did do, pushing two 100-character source annotations
     past the trailing edge, because those wrap budgets are counted in
     characters against an 11px advance.

     Scoped to 1100px, the page's own not-a-desktop threshold: the content
     column falls under the authored 720 somewhere around 1250px, and a
     1440px canvas gives it 853px, so no desktop reading width is touched. */
  .fig-svg { min-width: 720px; }

  /* …and give the scroller the whole screen to spend, not the text column.
     A figure is not prose and does not owe the measure anything. With the
     margins back, a tablet in either orientation clears 720 outright — iPad
     portrait goes from a 704px column to a 768px window, landscape from 608
     to 1024 — so the charts that were being shrunk now simply fit, with no
     pan at all. The text column is unmoved; only the plot bleeds. */
  .fig-plot { margin-inline: calc(-1 * var(--margin)); }

  /* A nested horizontal scroller a reader cannot see is a chart that looks
     broken. Where one is still needed, give it a persistent thin bar, and
     stop the horizontal swipe from escaping into browser back-navigation. */
  .fig-plot, .table-scroll {
    scrollbar-width: thin;
    scrollbar-color: var(--border) transparent;
    overscroll-behavior-x: contain;
  }
}

/* A handset cannot fit these figures at any size — 300 of their 720 units are
   label gutter — so the pan is a given and the only open question is whether
   what you pan across is readable. Spend the certainty on type. */
@media (max-width: 700px) {
  .fig-svg { min-width: 800px; }
}

@media (max-width: 900px) {
  :root { --2xl: 72px; --3xl: 104px; --margin: 32px; --gutter: 20px; }
  .content { grid-column: 1 / -1; }
  .rail {
    grid-column: 1 / -1;
    position: static;
    margin-block-start: var(--xl);
    padding-inline-start: 0;
    padding-block-start: var(--md);
    border-inline-start: 0;
    border-block-start: 1px solid var(--border);
  }
  .ledger-row { grid-template-columns: 2.5rem 1fr; }
  .ledger-body { grid-column: 2; }
  .section--opening { min-block-size: 0; display: block; }

  /* THE APPARATUS SCALE IS NOT VIEWPORT-NEUTRAL.
     The argument levels clamp and body copy is a physiological constant, so
     both already survive the phone. The apparatus does not: 11px caps and
     12px sources were sized for a 1440px canvas read at arm's length, and on
     a handset held at 30cm they are the smallest thing on the screen — which
     reads as a rendering fault rather than as hierarchy. Raise only the small
     end, through --meta-scale, so every label, chip, caption, table head and
     button moves together instead of drifting apart.

     This has to be restated for [lang='ar'], not inherited: that selector
     carries higher specificity than a bare :root and media queries add none,
     so the Arabic build would silently keep its desktop value. 1.08 × 1.14. */
  :root { --meta-scale: 1.14; }
  :root[lang='ar'] { --meta-scale: 1.23; }
  .act-index { font-size: 13px; }

  /* Tables: let the heads wrap rather than forcing a scroll on every table.
     Numeric heads stay nowrap — a figure broken across two lines stops being
     one number. */
  table.evidence th { white-space: normal; }
  table.evidence th.num, table.evidence td.num { white-space: nowrap; }
  table.evidence th, table.evidence td { padding: var(--sm) var(--xs); }

  /* THE CHROME WAS A FLOATING BAR OVER LIVE COPY.
     At desktop width it sits in the trailing margin and touches nothing. At
     phone width the same three buttons span most of the column and, being
     transparent with no reserved space, permanently obscured the last 68px
     of every screen — section headings, provenance chips and figure legends
     were all found underneath it. It becomes a real bar: opaque, hairlined,
     with its own height given back to the document. */
  .chrome {
    gap: 0;
    padding: 0;
    background: var(--surface);
    border-block-start: 1px solid var(--border);
    pointer-events: auto;
  }
  .chrome > .button-secondary,
  .chrome > .icon-button {
    flex: 1;
    justify-content: center;
    border: 0;
    border-inline-end: 1px solid var(--border);
    min-block-size: 52px;
    background: transparent;
  }
  .chrome > :last-child { border-inline-end: 0; }
  /* The bar's height is given back INSIDE the last section, not as page
     padding: the final section may carry an inverted ground, and a strip of
     page-coloured paper under it would read as the document having ended
     early. */
  .page > .section:last-child { padding-block-end: calc(var(--2xl) + 52px); }
  html { scroll-padding-block-end: 60px; }
}

@media (max-width: 560px) {
  :root { --margin: 20px; --2xl: 56px; --3xl: 80px; }
  .act-band .canvas { flex-direction: column; gap: var(--sm); }

  /* THE STRATA OVERFLOWED THE SCREEN, AND IT WAS THE LABELS DOING IT.
     .band is `max-content minmax(0,1fr)` with a nowrap label, so a band could
     never be narrower than "PROTOCOL GATEWAY" set solid — the taper set an
     inline-size the grid then refused to honour, and the widest bands ran off
     the edge. Below 560px the label takes its own row, which lets the taper
     mean what it says again. The taper itself is kept, only shallower: at
     350px the difference between 58% and 100% is the difference between a
     stack and five unrelated stripes, but 58% of 350px is not a column. */
  .band {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--xs);
    padding-block: var(--sm) var(--md);
  }
  .band-label, .outside-label, .waterline-label { white-space: normal; }
  .band-note { justify-self: stretch; }
  .band--tip { --band-span: 88%; --band-shoulder: 3.5%; }
  .band--signal { --band-span: 92%; --band-shoulder: 2.5%; }
  .band--knowledge { --band-span: 96%; --band-shoulder: 1.5%; }
  .band--logic { --band-span: 99%; --band-shoulder: 0.75%; }
  .band-caption { inline-size: 100%; align-items: baseline; }
  .band-leader { inline-size: var(--md); }
  .waterline { inline-size: 100%; }

  /* Five lines of display headline plus a lede plus the cue does not fit a
     667px handset. The headline is the argument and does not shrink; the air
     around it does. */
  .section--opening .scroll-cue { margin-block-start: var(--lg); }
  .figure-lead .qualifier { margin-block-start: var(--md); }
}

/* --- Touch --------------------------------------------------------------
   Keyed to the input, not the width: a 1024px tablet in landscape needs
   these and a 900px desktop window does not. The controls that stand alone
   clear the 44px floor. The inline citation links in the rail cannot — they
   sit inside an 18px provenance line and a 44px target there would overlap
   the line above and below, trading one defect for a worse one. They take
   the 24px AA floor instead, bought with padding rather than with type, so
   nothing in the reading column changes size.                             */

@media (pointer: coarse) {
  .button-secondary, .icon-button, .cta { min-block-size: 44px; }
  /* Padding, not flex: `display: flex` on a <summary> drops the disclosure
     marker, and the triangle is the whole reason a reader knows the table is
     there. 10px either side of a 26px line clears 44px on its own. */
  .fig-table > summary { padding-block: 10px; }
  .rail .source a {
    padding: 6px;
    margin: -6px;
    margin-inline-start: -2px;
  }
  .panel-link { padding-block: 12px; }
}

/* --- Print: the page as a filing ------------------------------------------ */

@media print {
  :root { --ground-0: #fff; --ground-1: #fff; --ground-2: #fff; }
  .chrome, .drawer, .depth-meter, .scroll-cue { display: none !important; }
  .section { padding-block: var(--lg); break-inside: avoid; }
  .rail { position: static; }
  .rule-section, .rule, .band-leader, .waterline-rule { transform: none !important; }
  .press, .act-band { clip-path: none !important; }
  .band, .band-caption { transform: none !important; }
  .band-label, .band-note, .outside-label, .band-caption-note { opacity: 1 !important; }
  a[href^='http']::after { content: ' (' attr(href) ')'; font-size: 10px; word-break: break-all; }
}
