/* ============================================================================================================
   system.css · ONE TYPE SYSTEM, ONE GRID, ONE PALETTE
   ------------------------------------------------------------------------------------------------------------
   Written after qa/r99-system.cjs measured the page at 1440x900 and found it had none of the three:

     · font files on disk 0, @font-face rules 0. All 158 text elements asked for "Inter", which is not
       installed, so the site rendered in the operating system's UI font (Segoe UI here, SF Pro on a Mac).
       design/关键画面契约.md §5 asks for a display face with character plus a clean text face.
     · 17 font sizes, 23 letter-spacings, 22 text colours, 14 different vertical gaps.
     · text left edges at 411 (hero, centred by its own max-width:820px + margin:auto), 101 (sections),
       18/0 (.questions), 321 (.partner) - four grids on one page.

   ORDER MATTERS IN THIS FILE AND IS DELIBERATE:
     §1 fonts   §2 tokens   §3 the grid   §4 DEFAULTS (section-scoped)   §5 ROLES (element-qualified, later,
     so equal specificity resolves to the role)   §6 colour   §7 rhythm   §8 controls.
   Every role selector is written with its own element name (p.hero-copy, p.section-index) or with the section
   id the base sheet uses (#meaning .team-name) because the base sheet addresses most roles through a class:
   a bare "html body .hero-copy" loses to ".hero-copy" plus a section id, and an over-broad default like
   "html body .section-shell p" silently flattens every paragraph role on the page - both mistakes were made
   in the first cut of this file and caught by qa/r99-detail.cjs (team names at 19px/400, the section label at
   16px, the lead and the body at the same size).
   ============================================================================================================ */

/* ------------------------------------------------------------------ §1 fonts (self-hosted; no runtime CDN) */
@font-face { font-family: "Space Grotesk"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("../assets/fonts/space-grotesk-400-2.woff2") format("woff2"); }
@font-face { font-family: "Space Grotesk"; font-style: normal; font-weight: 500; font-display: swap;
  src: url("../assets/fonts/space-grotesk-500-5.woff2") format("woff2"); }
@font-face { font-family: "Space Grotesk"; font-style: normal; font-weight: 600; font-display: swap;
  src: url("../assets/fonts/space-grotesk-600-8.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("../assets/fonts/inter-400-6.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 500; font-display: swap;
  src: url("../assets/fonts/inter-500-13.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 600; font-display: swap;
  src: url("../assets/fonts/inter-600-20.woff2") format("woff2"); }
/* The page sets ten CJK glyphs (the four Chinese names plus em dashes); subsetted to exactly those, 2.9 KB a
   weight, so the names are designed instead of falling through to whatever the visitor's OS picks. */
@font-face { font-family: "MOSI CJK"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("../assets/fonts/noto-sans-sc-400.woff2") format("woff2"); }
@font-face { font-family: "MOSI CJK"; font-style: normal; font-weight: 500; font-display: swap;
  src: url("../assets/fonts/noto-sans-sc-500.woff2") format("woff2"); }
@font-face { font-family: "MOSI CJK"; font-style: normal; font-weight: 600; font-display: swap;
  src: url("../assets/fonts/noto-sans-sc-700.woff2") format("woff2"); }

/* ------------------------------------------------------------------ §2 tokens */
:root {
  /* a display face for anything read as a mark (headlines, numbers, labels, navigation), a text face for
     anything read as a sentence; CJK follows both */
  --font-display: "Space Grotesk", "Inter", "MOSI CJK", "Segoe UI", system-ui, sans-serif;
  --font-text: "Inter", "MOSI CJK", "Segoe UI", system-ui, sans-serif;

  /* SEVEN STEPS - every size on the page is one of these */
  --t-display: clamp(42px, 4.6vw, 68px);
  --t-h2: clamp(28px, 3.1vw, 44px);
  --t-h3: 24px;
  --t-lead: 19px;
  --t-body: 16px;
  --t-small: 14px;
  /* THE SMALL END OF THE SCALE WAS TOO SMALL FOR ITS OWN RASTERISATION. Measured with qa/r107g-clone.cjs at
     device pixel ratio 1 - an ordinary 1080p monitor, and the client's own machine - a 12 px label at weight 500
     renders its stems with so little coverage that the darkest glyph pixel comes back at luminance 0.42 on the
     page's paper: 2.2:1, below even the large-text bar, no matter what colour the CSS asks for. 13 px at 600 and
     14 px for the rail are the sizes at which the ink actually arrives. (High-DPI screens flattered these sizes;
     the measurements here are at DPR 1 on purpose.) */
  --t-label: 13px;
  --t-meta: 14px;          /* the roster rail: small, but a size you can read without leaning in */

  /* THREE TRACKINGS */
  --track-tight: -0.022em; --track-normal: 0; --track-wide: 0.09em;

  /* one line height per step */
  --lh-display: 1.05; --lh-h2: 1.12; --lh-h3: 1.25; --lh-lead: 1.45;
  --lh-body: 1.62; --lh-small: 1.55; --lh-meta: 1.5; --lh-label: 1.35;

  /* THREE WEIGHTS: 400 reads, 500 marks, 600 insists */
  --w-read: 400; --w-mark: 500; --w-insist: 600;

  /* THE SPACING SCALE */
  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 24px;
  --s6: 32px; --s7: 48px; --s8: 64px; --s9: 96px; --s10: 120px;

  /* ONE MARGIN, and the measure a paragraph is allowed to run to */
  --gutter: clamp(20px, 7vw, 120px);
  --measure: 62ch;

  /* TWO SURFACES, three levels each, one accent each - was 22 text colours */
  /* the levels were set for text on paper; over the scene the body level needed a step more light to hold
     AA everywhere (measured: the capabilities list sat at 4.49:1 with #C2CEDF) */
  --c-ink: #F6F9FF; --c-ink-2: #CBD6E6; --c-ink-3: #9CAFC6;
  --c-paper-ink: #0B0C12; --c-paper-ink-2: #4A5060; --c-paper-ink-3: #6B7385;
  --c-accent: rgb(140, 192, 255);
  --c-paper-accent: #4338CA;
  --c-warm: rgb(255, 159, 10);
}

/* ------------------------------------------------------------------ §3 the grid */
html body .section-shell { padding-left: var(--gutter); padding-right: var(--gutter); }
/* the hero was centred by its own max-width:820px + margin:auto, so the first screen stood 310 px to the
   right of every section below it; it sits on the page margin now */
html body .hero, html body .partner { max-width: none; }
html body .questions { padding-left: var(--gutter); padding-right: var(--gutter); max-width: none; }
html body .site-header, html body .site-footer { padding-left: var(--gutter); padding-right: var(--gutter); }
html body #brand .brand-inner { max-width: min(1020px, 100%); }
/* every two-column block starts its second column at the same x (was 604 / 622 / 676) */
/* r102 · 03 AND 04 ARE TWO SECTIONS, NOT TWO COLUMNS. Side by side they sat 0.007 of scroll apart (measured:
   p=0.567 and p=0.574), so no pair of cameras could ever hold them apart - they were the same frame by
   construction. Stacked, each owns its own beat, its own structure and its own camera. */
html body .cycle { grid-template-columns: minmax(0, 1fr) !important; gap: 0; }
/* each numbered block owns a screen. Stacked with a 64 px gap the two labels landed 0.011 of scroll apart, which
   is no separation at all - and a camera beat needs about a fifth of a screen to hold. Every block in this
   section is centred in its own 76vh band, which is also where the page gets its air. */
html body #cycle > .principles, html body #cycle > .capabilities, html body #cycle > .cycle-intro {
  min-height: 76vh; display: flex; flex-direction: column; justify-content: center; }

/* ------------------------------------------------------------------ §4 defaults (section-scoped) */
/* NOTE: no bare "a" in this list. Including it repainted every button label with the body colour: measured
   with qa/r99-contrast2.cjs, .button-primary came back as rgb(194,208,223) on a #F7F8FF fill - 1.6:1, the
   least legible text on the page, and it is the main call to action. Links keep the colour their own rule sets. */
html body .section-shell p, html body .section-shell li,
html body .questions p, html body .questions li, html body .brand-piece p {
  font-family: var(--font-text); font-size: var(--t-body); line-height: var(--lh-body);
  font-weight: var(--w-read); letter-spacing: var(--track-normal); color: var(--c-ink-2);
}
html body .section-shell h2, html body .brand-piece h2 {
  font-family: var(--font-display); font-size: var(--t-h2); line-height: var(--lh-h2);
  font-weight: var(--w-mark); letter-spacing: var(--track-tight); color: var(--c-ink); }
html body .section-shell h3 {
  font-family: var(--font-display); font-size: var(--t-h3); line-height: var(--lh-h3);
  font-weight: var(--w-mark); letter-spacing: var(--track-tight); color: var(--c-ink); }
/* one surface now: the .section-light colour variants went with the sheet */

/* ------------------------------------------------------------------ §5 roles (element-qualified; later wins) */
/* display roles */
html body .hero h1, html body h1.hero-title { font-family: var(--font-display); font-size: var(--t-display);
  line-height: var(--lh-display); font-weight: var(--w-mark); letter-spacing: var(--track-tight); color: var(--c-ink);
  /* 548 px of column at 75 px is seven characters a line; display type wants a wider measure than body copy */
  max-width: min(840px, 60vw); text-wrap: balance; }
/* the headline's own wrapper carries the same 547 px cap, so widening the h1 alone did nothing: measured
   line widths stayed 172 / 154 / 232 px at 66 px type - five short lines of display type. */
html body .hero > .fx { max-width: min(840px, 60vw); }
html body .hero h1 .w { color: var(--c-ink); }
html body .meaning h2, html body .cycle h2, html body .partner h2, html body .students-inner h2,
html body .pilot-inner h2, html body .brand-piece h2 { text-wrap: balance; }
html body h3.cap-title { font-size: var(--t-h3); }
html body p.motto { font-family: var(--font-display); font-size: var(--t-h3); line-height: var(--lh-h3);
  font-weight: var(--w-mark); letter-spacing: var(--track-tight); color: var(--c-ink); }
html body p.team-name { font-family: var(--font-display); font-size: var(--t-h3); line-height: var(--lh-h3);
  font-weight: var(--w-insist); letter-spacing: var(--track-tight); }
html body #meaning .team-name { font-size: var(--t-h3); font-weight: var(--w-insist);
  font-family: var(--font-display); letter-spacing: var(--track-tight); color: var(--c-ink); }
html body a.wordmark { font-family: var(--font-display); font-size: 20px; font-weight: var(--w-insist);
  letter-spacing: var(--track-wide); }

/* reading roles */
html body p.hero-copy, html body p.team-lede { font-size: var(--t-lead); line-height: var(--lh-lead);
  font-weight: var(--w-read); letter-spacing: var(--track-normal); color: var(--c-ink); text-wrap: pretty; }
html body p.hero-detail { font-size: var(--t-body); line-height: var(--lh-body);
  letter-spacing: var(--track-normal); color: var(--c-ink-2); text-wrap: pretty; }
html body #meaning p, html body #meaning li { text-wrap: pretty; }
/* the 15 px paragraph: ".section-light .meaning-panel p" (0,2,1) and ".cycle-intro p" outrank a bare role rule */
html body .meaning-panel p, html body .cycle-intro p,
html body #meaning .meaning-panel p { font-size: var(--t-body); line-height: var(--lh-body); }
html body #cycle .cycle-intro p { font-size: var(--t-body); line-height: var(--lh-body); }
html body p.team-lede, html body .meaning-panel p.team-lede { font-size: var(--t-lead); line-height: var(--lh-lead); }
html body p.team-fact { font-size: var(--t-small); line-height: var(--lh-small); max-width: var(--measure); }
html body #meaning p.team-fact, html body #meaning .team-fact { font-size: var(--t-small);
  line-height: var(--lh-small); color: var(--c-ink-2); }
html body p.team-role, html body #meaning .team-role { font-size: var(--t-body); line-height: var(--lh-body);
  font-weight: var(--w-insist); color: var(--c-paper-ink); }
html body #meaning p.team-where, html body #meaning .team-where, html body p.team-where {
  /* was 11 px all-caps with 0.09em tracking in a 190 px rail: "GENEVA · THE UK · HONG KONG — STRATEGY AND
     PROGRAMME DEVELOPMENT" broke into four ragged lines and orphaned "KONG". Sentence case, 13 px, no
     tracking: it is metadata and it should read. */
  font-family: var(--font-text); font-size: var(--t-meta); line-height: var(--lh-meta);
  font-weight: var(--w-read); letter-spacing: var(--track-normal); text-transform: none;
  color: var(--c-ink-3); }
html body #meaning p.team-tags, html body #meaning .team-tags { font-family: var(--font-text);
  font-size: var(--t-meta); line-height: var(--lh-meta); letter-spacing: var(--track-normal); color: var(--c-accent); }

/* mark roles: numbers, labels, navigation, buttons - one size, one tracking, one weight */
html body p.section-index, html body p.section-index .num, html body p.section-index .lbl,
html body .section-shell p.eyebrow, html body p.eyebrow, html body .team-head,
html body span.stage-number, html body p.stage-caption, html body .stage-caption,
html body nav, html body nav a, html body .site-header nav a, html body a.header-cta, html body a.button, html body .button,
html body .pilot-facts span, html body .waypoint, html body .waypoint__n, html body .waypoint__t,
html body .scene-anchor__label, html body .frame-selector, html body .frame-tabs button, html body .deck-rail {
  font-family: var(--font-display); font-size: var(--t-label); line-height: var(--lh-label);
  letter-spacing: var(--track-wide); text-transform: uppercase; font-weight: var(--w-mark); }
html body p.section-index .num, html body .pilot-facts span, html body .waypoint__n,
html body span.stage-number { font-weight: var(--w-insist); }
html body .waypoint__n, html body .waypoint__t, html body .scene-anchor__label { color: var(--c-accent); }

/* ------------------------------------------------------------------ §5b composition: full-bleed dark, text on the scene
   The user's decision was "整套重做" - the paper sheet goes. The two sections that carried it (.section-light)
   are back on the dark surface, which also restores ".scrim::before" (the base sheet disables the veil inside a
   light section, because paper did not need one). What used to keep the type readable was the paper; what keeps
   it readable now is the veil under each block plus a shadow on the type, with the measures from §5. */
html body #meaning, html body #partner { background: none; min-height: auto; }
html body #meaning { grid-template-columns: minmax(0, 1fr); padding-top: var(--s9); padding-bottom: var(--s10); }
html body #meaning .meaning-panel { max-width: min(780px, 52vw); padding: 0; }
html body #partner { padding-top: var(--s9); padding-bottom: var(--s10); }
html body #partner .partner-inner { max-width: min(780px, 52vw); }
/* THE VEIL IS A BLURRED PLATE, NOT A VIGNETTE. A radial gradient is strongest at the block's centre and fades
   to nothing at its left and right edges - measured with qa/r99-stars.cjs: 34 px inside the right edge of
   .questions the field read mean 121 with sd 25, i.e. the scene showing through at full strength exactly where
   the longest lines of copy end. A solid plate with a blurred edge covers the block evenly and still dissolves
   into the scene instead of ending on a seam. */
html body .scrim::before { inset: -6% -3%; background: rgba(4, 11, 26, .86); filter: blur(26px); }
/* THE PAGE WASH. With the sheet gone the copy sits on the scene, and the scene is not equally dark everywhere:
   measured with qa/r99-contrast3.cjs, the capabilities list and the learning-cycle copy sat at 3.2:1 and 3.5:1
   where the bright water runs behind them, while the same type reads 8-18:1 over the dark rock. A wash across
   the full frame - strongest behind the text zone, releasing the right quarter so the world is still seen -
   is what makes one page out of two very different backgrounds. The per-block veil above stays on top. */
/* (a full-frame wash would have to sit above the canvas but below the type; the canvas is #stage at z-index 0
   and every section is z-index 1, so a body-level wash lands UNDER the scene and does nothing - measured: the
   capabilities list stayed at 3.2:1 with the wash in place. The veil on each block is the layer that actually
   sits between the scene and the text, so that is where the darkening belongs.) */
html body .section-shell p, html body .section-shell li, html body .section-shell h1,
html body .section-shell h2, html body .section-shell h3 { text-shadow: 0 1px 22px rgba(4, 8, 20, .8); }
/* the hairline was the sheet's edge; with no sheet it is a line drawn through the scene */
html body::after { display: none; }
/* the roster was drawn on paper: hairlines and ink flip to the dark surface */
html body #meaning .team-card { border-top-color: rgba(158, 193, 232, .22); }
html body #meaning .team-role { color: var(--c-ink); }
/* the Chinese name is a <span> inside the name and kept the paper-era ink: measured rgb(74,80,96) on #060A18 =
   2.45:1 with qa/r106b-ink.cjs, the only text on the page under 4.5:1. It is the second line of the name, so it
   takes the same muted ink as the rest of the roster's secondary text. */
html body #meaning .team-name span { color: var(--c-ink-3); }
/* (the rail's colours live in §5 with the other element-qualified roles) */

/* ------------------------------------------------------------------ §6 colour: one palette, one surface */
html body { color: var(--c-ink); }
html body p.section-index, html body p.section-index .num, html body p.eyebrow { color: var(--c-accent); }
html body p.section-index .lbl, html body p.stage-caption { color: var(--c-ink-3); }
/* (the paper-era rules for the rail are gone: the rail is on the dark surface) */
html body h2 em { color: var(--c-accent); font-style: italic; }

/* ------------------------------------------------------------------ §6a prose that carries the argument
   r104 · THE PAGE WAS 56 LIST ITEMS AND 7 PARAGRAPHS. Three of the six enumerations were not lists at all: the
   five principles are a stance, the three outcomes are things that happened, and section 04's six capabilities
   were a second copy of section 05's six stages (four of the six words identical). They are prose now, and the
   six capability words are still handles on the world - hovering one lights the pillar that stage owns. */
html body .principles-stance { font-family: var(--font-display); font-size: var(--t-lead);
  line-height: var(--lh-lead); font-weight: var(--w-mark); letter-spacing: var(--track-tight);
  color: var(--c-ink); max-width: 34ch; margin: 0 0 var(--s4); }
html body .principles-terms { font-size: var(--t-small); line-height: var(--lh-small); color: var(--c-ink-3);
  max-width: 70ch; margin: 0; }
html body .cap-prose, html body .outcome-prose { font-size: var(--t-body); line-height: var(--lh-body);
  color: var(--c-ink-2); max-width: 62ch; margin: 0; }
html body .questions-lede { font-size: var(--t-lead); line-height: var(--lh-lead); color: var(--c-ink);
  max-width: 46ch; margin: 0 0 var(--s5); }
/* THE HANDLES. A dotted underline is the only affordance on the page that says "this word is a thing in the
   world"; hovering it lights that structure, the same one the cycle list lights for the same step. */
html body .cap { color: var(--c-accent); border-bottom: 1px dotted rgba(140, 192, 255, .55);
  transition: color var(--mo-base, 340ms) var(--mo-ease, ease), border-color var(--mo-base, 340ms) var(--mo-ease, ease); }
html body .cap:hover, html body .cap:focus-visible { color: #FFFFFF; border-bottom-color: #FFFFFF; }

/* ------------------------------------------------------------------ §6b the lists are the design now
   The page carried 1364 words; the lists explained themselves twice over ("Human understanding — Begin with
   people, experiences and contexts"), which is what "文字堆砌" was reading. The explanations are gone
   (qa/r100-volume.cjs: 1364 -> 706 words on the page), so the terms are set as type: display face, one size
   from the scale, a hairline between rows and no bullet glyph. */
html body .principle-list, html body .cap-list { list-style: none; padding: 0; margin: var(--s5) 0 0; }
html body .principle-list li, html body .cap-list li, html body .cycle-list li {
  list-style: none; padding: var(--s4) 0; margin: 0; border-top: 1px solid rgba(158, 193, 232, .16); }
html body .principle-list li::before, html body .cap-list li::before,
html body .outcome-list li::before { display: none; }
html body .principle-list li:first-child, html body .cap-list li:first-child,
html body .cycle-list li:first-child { border-top: 0; padding-top: 0; }
html body .principle-list li, html body .cap-list li {
  font-family: var(--font-display); font-size: var(--t-h3); line-height: var(--lh-h3);
  font-weight: var(--w-mark); letter-spacing: var(--track-tight); color: var(--c-ink); padding-left: 0; }
html body .cycle-list li { display: grid; grid-template-columns: 56px minmax(0, 1fr); align-items: baseline; }
html body .cycle-list h3 { font-size: var(--t-h3); }

/* ------------------------------------------------------------------ §7 rhythm: only the scale */
html body p.eyebrow, html body p.section-index { margin-bottom: var(--s4); }
html body p.hero-copy { margin: var(--s5) 0 var(--s5); }   /* the subhead and the body were 14 px apart */
html body .hero-detail { margin: 0; }
html body .hero-actions { margin-top: var(--s7); gap: var(--s4); }
html body .team-head { margin-top: var(--s8); }
html body p.team-lede { margin: 0 0 var(--s4); }
html body .team-grid { margin-top: var(--s5); gap: 0; }
html body .capabilities, html body .questions { margin-top: var(--s8); }
/* with half the copy gone there is room for air: the sections are further apart and each block sits on its own */
html body #cycle { row-gap: var(--s9); padding-top: var(--s9); padding-bottom: var(--s9); }
html body #students, html body #pilot, html body #brand { padding-top: var(--s9); padding-bottom: var(--s9); }
html body #meaning .team-head { margin-top: var(--s9); }
html body .principle-list li, html body .cap-list li, html body .cycle-list li { padding: var(--s5) 0; }
html body .cycle-list { margin-top: var(--s6); }
html body p.cycle-note { margin-top: var(--s6); }
html body .format-list, html body .pilot-facts { margin-top: var(--s5); gap: var(--s4); }
html body .principle-list li, html body .cap-list li, html body .question-list li,
html body .outcome-list li, html body .cycle-list li { margin-bottom: var(--s3); }
html body .pilot-inner > .button { margin-top: var(--s6); }
html body .site-footer { padding-top: var(--s6); padding-bottom: var(--s6); }

/* ------------------------------------------------------------------ §8 controls */
html body a.button, html body .button { font-size: var(--t-label); padding: 15px var(--s5); border-radius: 2px;
  font-family: var(--font-display); letter-spacing: var(--track-wide); text-transform: uppercase; font-weight: var(--w-mark); }
html body .button.button-primary { color: #0B1030; }
html body .button.button-quiet { color: var(--c-ink); }
/* with the sheet gone there is one surface: the light button keeps its dark ink */
/* the rail had 190 px: "GENEVA · THE UK · HONG KONG — STRATEGY AND PROGRAMME DEVELOPMENT" and the discipline
   lists broke to three and four lines with orphaned tails. 220 px and one step darker grey. */
html body #meaning .team-card { grid-template-columns: 220px minmax(0, 1fr); }
html body .section-light { background: none; }
/* (superseded by the ID-scoped rail rules above) */
/* r102 · STACKED FOR CERTAINTY. The same declaration at §3 did not take effect (measured: #cycle still reported
   two tracks with the rule present and !important), so it is repeated here at the end of the sheet, where
   nothing in this file can precede it. */
/* r102 · AND WHY THIS IS "display: block" AND NOT "grid-template-columns: 1fr": the section's own
   .cycle-note carries "grid-column: 2", which forces a second IMPLICIT track, and auto-placed siblings then
   fill row 1 column 2 - so the two numbered blocks stayed side by side through a single-column template AND
   through !important (measured: #cycle reported two tracks, the two labels 11 px apart, and the min-heights
   applied but the stacking did not). Block flow has no implicit tracks to fight. */
html body #cycle { display: block; }
html body #cycle .cycle-note { grid-column: auto; }

/* ================================================================== §9 · THE TYPE STANDS ON GLASS (r108)
   The client, on the r106/r107 build: "你这个浅色的文字背景还能看得清什么东西 用透明的啊". He is right, and the
   fault is the composition rather than the contrast: an opaque page hides the world the copy is describing. So
   the copy no longer has a page. Three layers make the words readable without one:
     1. a graduated wash across the text zone, dense at the left margin and gone by the time it reaches the
        window on the right, so the landscape is visible through the copy and beside it;
     2. a blurred translucent plate behind each block (.scrim::before, 20 px of backdrop blur) - glass, not
        paper: what is behind the words is the world, defocused;
     3. type that now carries its own weight - the sizes and weights raised in r107, 13 px labels with a
        quarter-pixel stroke, and a shadow that separates the letters from whatever the camera is showing.
   The ink tokens go back to the light palette: the roles (§5, §6) are unchanged, they are simply read against
   a translucent surface now. Every number below is measured with qa/r106i-glyph.cjs on the glyphs themselves.
   ---------------------------------------------------------------------------------------------------------- */

html body {
  /* the text zone's right edge: the wash releases here and the world takes the rest of the frame */
  --paper-col: min(56vw, 880px);
  --paper-text: calc(var(--paper-col) - var(--gutter) - var(--s5));
}

html body .page-paper {
  position: relative; z-index: 1;
  --c-ink: #F6F9FF; --c-ink-2: #D3DEEE; --c-ink-3: #A8BCD4; --c-accent: rgb(150, 198, 255);
  color: var(--c-ink);
}

/* THE WASH. z-index MUST BE NEGATIVE, NOT 0: a positioned pseudo-element at z-index 0 paints in the same phase
   as other positioned descendants, and that phase sits ABOVE static in-flow content - measured with
   qa/r107i-bisect.cjs, the section's own first <p> (.section-index) rendered through the layer at luminance
   0.27 against 0.93, 3.8:1 for a colour that is 9:1. Everything inside a position:relative wrapper was fine,
   which is exactly why only some labels washed out. At -1 the wash sits under all of the section's content and
   still above the canvas. */
html body .page-paper > section:not(#partner)::before,
html body .page-paper > .questions::before {
  content: ""; position: absolute; z-index: -1; top: 0; bottom: 0;
  /* THE BOX MUST END WHERE THE INK ALREADY HAS. Two vision passes read a seam at 38-57% of the frame: the
     gradient was still carrying ~0.2 alpha at the element's own right edge (the radial's last stop landed at
     x=1929, far outside a box that ends at 806), so a semi-transparent layer simply stopped. The box is now
     wider than the wash and the ink reaches zero at 82% of it - the tail is fully transparent, there is no
     boundary to see, and the blocks' own halos carry the contrast where the words are. */
  left: -100vw; width: calc(100vw + var(--paper-col) + var(--s10));
  /* THE SECTION WASH IS GONE (r110). It was anchored to the left 56% of the frame, which is the same thing as
     saying "no block may stand anywhere else": with it in place a centred statement or a right-hand column sat
     outside its darkening. The blocks' own halos were already doing the work (measured with the wash in place,
     the text sat on 0.004-0.009 against a halo centre of .58), so the layout is free now - the shape of a
     section comes from where its blocks stand, and the halo travels with each block. */
  background: none;
}
html body .page-paper > .questions { position: relative; }

/* THE GLASS PLATES. Each block already carried .scrim; on the page it was disabled, on glass it is the layer
   that makes 20 px of defocus stand between the words and the landscape. */
html body .page-paper .scrim::before {
  /* THE EDGE WAS THE WHOLE COMPLAINT. A radial whose last stop lands ON the element's border still ends in a
     visible straight line - measured by a vision pass as "a clean vertical boundary at x ~ 870-900 ... it reads
     as a tiled layout (dark text column + image column)". The halo is now much larger than the words and the
     ink reaches zero at 86% of it, so the darkening dissolves inside its own box. */
  display: block; inset: -22% -28% -22% -30%;
  /* the plate is a radial centred on the words and gone by its edges: a uniform fill is what a reader sees as a
     card, and 30 px of backdrop blur is what turns "behind the text" into the world itself, defocused */
  /* the same arithmetic as the wash: the ink must reach zero INSIDE the box, so 26% (centre) + 62% (radius)
     = 88% of the element, with the last 12% carrying nothing at all */
  background: radial-gradient(62% 94% at 26% 50%, rgba(4, 11, 26, .58) 0%, rgba(4, 11, 26, .42) 44%,
    rgba(4, 11, 26, .15) 72%, rgba(4, 11, 26, 0) 100%);
  /* NO BACKDROP-FILTER. The blur is what a reader sees as the panel's edge: backdrop-filter cannot feather -
     its effect stops dead at the element's border, so 34 px of defocus inside and a sharp world outside read as
     a seam however soft the fill is ("you can point at a hard vertical boundary", two vision passes running).
     A feathered wash can fade on every side; a blurred rectangle cannot. Readability is carried by the wash,
     the type's own weight and the shadow instead - all of which are measured. */
  backdrop-filter: none; -webkit-backdrop-filter: none; filter: none;
}
/* the copy column never runs past the wash's dense half */
html body #meaning .meaning-panel, html body #partner .partner-inner,
html body #cycle .cycle-intro, html body #cycle .cycle-list, html body #cycle .cycle-note,
html body #cycle .capabilities, html body #questions .questions, html body #pilot .pilot-inner,
html body #students .outcome-list, html body #brand .brand-inner, html body #meaning .team-grid {
  max-width: var(--paper-text); }
html body .page-paper h1, html body .page-paper h2, html body .page-paper h3 { max-width: var(--paper-text); }
html body .page-paper .section-shell p, html body .page-paper .section-shell li,
html body .page-paper .section-shell h1, html body .page-paper .section-shell h2,
html body .page-paper .section-shell h3, html body .page-paper .brand-piece p {
  text-shadow: 0 1px 22px rgba(4, 8, 20, .85); }
html body .page-paper .principle-list li, html body .page-paper .cap-list li,
html body .page-paper .cycle-list li, html body .page-paper #meaning .team-card,
html body .page-paper .question-list li, html body .page-paper .outcome-list li {
  border-top-color: rgba(158, 193, 232, .18); }
html body .page-paper .cap { border-bottom-color: rgba(140, 192, 255, .55); }
html body .page-paper .cap:hover, html body .page-paper .cap:focus-visible { color: #FFFFFF; border-bottom-color: #FFFFFF; }
html body .page-paper h2 em, html body .page-paper .meaning em, html body .page-paper p em {
  color: var(--c-accent); text-shadow: 0 1px 22px rgba(4, 8, 20, .85); }
/* THE LABELS CARRY A STROKE. Measured at device pixel ratio 1 - an ordinary monitor, the client's - a 13 px
   label with .13em tracking renders its stems about one pixel wide and the darkest pixel came back at
   luminance 0.28-0.42: 2-3:1 however light the colour is (qa/r107c-map.cjs, qa/r107g-clone.cjs). */
html body .page-paper p.section-index { color: var(--c-accent); font-weight: 700;
  letter-spacing: var(--track-wide); -webkit-text-stroke: .25px currentColor; }
html body .page-paper p.section-index .lbl { color: var(--c-ink-3); -webkit-text-stroke: 0; }
html body .page-paper .button-primary { background: #F7F8FF; color: #0B1030; box-shadow: 0 16px 44px rgba(5, 8, 20, .55); }
html body .page-paper .button-primary:hover { background: #FFFFFF; color: #0B1030; }
html body .page-paper .button-quiet { background: rgba(9, 21, 43, .6); color: #F6F9FF;
  border-color: rgba(158, 193, 232, .46); backdrop-filter: blur(6px); }
html body .page-paper .button-quiet:hover { background: rgba(14, 30, 58, .72); color: #FFFFFF; }
/* THE ROSTER'S WHITE GLASS CARD IS GONE FOR GOOD. styles.css:725 draws .team-card as rgba(255,255,255,.55)
   with a dark hairline - a card for the paper era. On a phone it was a white panel carrying the page's light
   ink (13 of the phone's 18 contrast failures, 1.6-3.0:1, qa/r107a-mobile.cjs). r100 turned the roster into a
   rail and hairlines; the surface was a leftover. */
html body .team-card { background: none; border: 0; border-top: 1px solid rgba(158, 193, 232, .16); }
/* the footer is the world's bar again */
html body .site-footer { background: rgba(5, 13, 30, .92); backdrop-filter: blur(8px);
  border-top-color: rgba(158, 193, 232, .26); color: #A8BCD4;
  grid-template-columns: minmax(0, 140px) minmax(0, calc(var(--paper-text) - 140px - var(--s5)));
  justify-content: start; }
html body .site-footer p:first-child { color: var(--warm, #FF9F0A); }

/* mobile: no wash at all (there is no column to share with the world on a 390 px screen) - the plates carry
   the whole job at .94, and the hero gets its own bottom-weighted sheet */
@media (max-width: 900px) {
  html body .page-paper > section:not(#partner)::before, html body .page-paper > .questions::before { display: none; }
  html body .page-paper .scrim::before { background: rgba(4, 11, 26, .94); }
  html body .page-paper .brand-piece__copy { background: rgba(4, 11, 26, .9); backdrop-filter: blur(10px); padding: var(--s6) var(--s5); }
  /* the 07 questions block is a bare child of the wrapper, not inside a section, so the phone has no section
     wash for it and it had no plate of its own: measured 1.7-3.5:1 on five blocks (qa/r107a-mobile.cjs) */
  html body .page-paper > .questions { position: relative; background: rgba(4, 11, 26, .9);
    backdrop-filter: blur(10px); padding: var(--s6) var(--s5); }
  html body .hero .eyebrow { display: inline-block; background: rgba(4, 11, 26, .74);
    padding: 7px 12px; margin-left: -12px; border-radius: 2px; }
  html body .hero::before {
    content: ""; display: block; position: absolute; inset: 0; z-index: -1; pointer-events: none;
    background: linear-gradient(180deg, rgba(6, 10, 24, 0) 0, rgba(6, 10, 24, .30) 22vh,
      rgba(6, 10, 24, .88) 46vh, rgba(6, 10, 24, .94) 100%); }
  html body .site-footer { grid-template-columns: 1fr; }
}
/* ================================================================== §10 · TEXT ARRIVES WORD BY WORD (r106)
   The client asked for 文字动效 twice and had a 340 ms fade with a 12 px lift: correct by the motion budget and
   invisible in practice. A headline now rises out of a mask, one word at a time - the word sits in a clipping
   box and travels up from below it, so the type is uncovered rather than faded in. transform only, 460 ms a
   word, 42 ms apart (#4 in the motion contract: 100-500 ms per element), and nothing at all under
   prefers-reduced-motion. The spans are created at runtime by js/text-motion.js, so with JS off the headline is
   simply a headline: the failure mode is a static headline, never an invisible one. */
html body .w, html body .hero h1 .w { display: inline-block; overflow: hidden;
  padding: 0 .06em .18em; margin: 0 -.06em -.18em;      /* room for descenders and italic inside the mask */
  opacity: 1; transform: none; }
html body .wi { display: inline-block; transform: translate3d(0, 126%, 0); will-change: transform;
  transition: transform 460ms cubic-bezier(.22, .68, .26, 1); transition-delay: calc(var(--i, 0) * 42ms); }
html body .typed .wi, html body .tm-in .wi { transform: none; }
/* the blocks arrive a touch further and a touch slower than they used to: the lift was 18 px and is now 26 */
html body .reveal { --mo-lift: 26px; }
html body .line-in { opacity: 0; transform: translateY(20px);
  transition: opacity 320ms var(--mo-ease, ease-out), transform 500ms var(--mo-ease, ease-out); }
html body .line-in.in-view { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  html body .wi { transform: none !important; transition: none !important; }
  html body .line-in, html body .reveal { opacity: 1 !important; transform: none !important; }
}
/* ================================================================== §11 · THE PAGE STOPS COUNTING (r109)
   The second half of the client's note: "而且现在这个文字排版还是列举堆砌式". The page was five sections of the same
   shape - a label, a heading, and three to six uniform rows - so the reader met a list before they met an idea.
   Each of the enumerative blocks is now a different editorial form, and the DOM hooks the world wiring depends on
   (.cycle-list li, .question-list li, .pilot-facts li - js/world-links.js:78-84) are untouched: only the shape of
   the blocks changes, so hovering a verb still lights its pillar.

   05 · SIX STAGES AS ONE RUN. The cycle is a sequence, so it is set as a line of six verbs that wraps, numbered
   above the line - not six boxes stacked. The numbers keep the order legible without a list to read. */
html body .cycle-list { display: block; line-height: 1.4; margin-top: var(--s6); }
html body .cycle-list li { display: inline; border: 0; padding: 0; margin: 0 .42em 0 0; background: none; }
html body .cycle-list li h3 { display: inline; font-family: var(--font-display);
  font-size: clamp(25px, 2.5vw, 38px); line-height: 1.35; font-weight: var(--w-mark); letter-spacing: var(--track-tight); }
html body .cycle-list li .stage-number { font-size: var(--t-label); font-weight: 700; color: var(--c-accent);
  vertical-align: 1.5em; margin-right: .25em; -webkit-text-stroke: .25px currentColor; }

/* 07 · THREE QUESTIONS ARE THE FEATURE, THE REST IS A SENTENCE. Six uniform rows is exactly what "列举堆砌" named;
   three are set as display questions with their theme above, and the other three themes are told in one line, so
   nothing is lost and nothing is counted. */
html body .question-list { list-style: none; margin: var(--s6) 0 0; padding: 0; }
html body .question-list li { border: 0; padding: 0; margin: 0; }
html body .question-list li.q-lead { padding: var(--s5) 0; margin: 0; max-width: 40ch;
  border-top: 1px solid rgba(158, 193, 232, .18); font-family: var(--font-display);
  font-size: clamp(20px, 1.9vw, 29px); line-height: 1.3; letter-spacing: var(--track-tight); color: var(--c-ink); }
html body .question-list li.q-lead strong { display: block; margin-bottom: var(--s3); font-family: var(--font-text);
  font-size: var(--t-label); font-weight: 700; letter-spacing: var(--track-wide); text-transform: uppercase;
  color: var(--c-accent); -webkit-text-stroke: .25px currentColor; }
html body .question-list li.q-rest { margin-top: var(--s5); max-width: 64ch; font-size: var(--t-small);
  line-height: var(--lh-small); color: var(--c-ink-3); }
html body .question-list li.q-rest strong { color: var(--c-ink-2); font-weight: 600; }

/* 08 · THE NUMBERS ARE THE LAYOUT. Three facts in a column of nearly identical sentences read as a list; the same
   three as figures over their own captions read as a specification. */
html body .pilot-facts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s6);
  list-style: none; margin-top: var(--s7); padding: var(--s5) 0 0; border-top: 1px solid rgba(158, 193, 232, .18); }
html body .pilot-facts li { display: block; border: 0; margin: 0; padding: 0; max-width: 30ch;
  font-size: var(--t-small); line-height: var(--lh-small); color: var(--c-ink-3); }
html body .pilot-facts li span { display: block; margin-bottom: var(--s3); font-family: var(--font-display);
  font-size: clamp(32px, 3.2vw, 52px); line-height: .95; font-weight: var(--w-mark);
  letter-spacing: var(--track-tight); color: var(--c-ink); }
/* 03 · the five terms are a sentence, not five middots */
html body .principles-terms { max-width: 62ch; }

@media (max-width: 900px) {
  html body .pilot-facts { grid-template-columns: 1fr; gap: var(--s5); }
  html body .question-list li.q-lead { max-width: none; }
}

/* ================================================================== §12 · EVERY SECTION HAS ITS OWN SHAPE (r110)
   The judge on the de-enumerated build: "Varied or uniform stack? Uniform. Everything lives in one left rail at
   one cadence: paragraph, mantra, eyebrow, headline, subhead, stat row, button ... No right column, no marginalia,
   no scale break ... Does it read as editorial? No". The blocks stopped being lists in r109; the page was still
   ten repetitions of one column. Four shapes now, and the world moves to the other side where the copy does
   (the lens-shift profile in src/app.js is per section, so "the text is on the right here" is a camera decision
   as much as a typographic one). */
/* A · 03 · THE STATEMENT AND ITS MARGIN. The stance is set as a display statement and the five terms become a
   note in the margin beside it - a page-turn, not a paragraph. */
html body #cycle .principles { display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
  column-gap: var(--s8); align-items: end; }
html body #cycle .principles .section-index { grid-column: 1 / -1; }
html body #cycle .principles-stance { grid-column: 1; font-size: clamp(28px, 3.2vw, 46px); line-height: 1.1;
  max-width: 22ch; margin: 0; }
html body #cycle .principles-terms { grid-column: 2; max-width: 34ch; margin: 0; padding-bottom: .35em;
  border-left: 1px solid rgba(158, 193, 232, .18); padding-left: var(--s5); }
/* B · 05 · THE RUN CROSSES THE PAGE. The six stages were a column inside the text zone; as a run they can use the
   whole measure, which is what makes the cycle read as a sequence rather than a block of type. */
html body #cycle .cycle-list { max-width: min(1000px, 88%); }
html body #cycle .cycle-note { max-width: 30ch; margin-left: auto; text-align: right; }
/* C · 06 · THE COPY CHANGES SIDES. Students' outcomes are the only block on the right half of the frame, with the
   world's structures taken to the left for that beat (LENS_SHIFT_SECTIONS: students -0.42). */
html body #students .students-inner { margin-left: auto; max-width: 46ch; padding-top: var(--s8); }
html body #students h2 { max-width: 24ch; }
/* D · 08 · THE SPECIFICATION'S OWN COLUMN. The pilot's numbers stay as the wide row; the sentence that explains
   them moves to the right, so the section has two axes instead of one. */
html body #pilot .pilot-inner > p:not(.section-index) { margin-left: auto; max-width: 40ch; }
html body #pilot .pilot-inner > .button { margin-left: auto; display: table; }

@media (max-width: 900px) {
  html body #cycle .principles { display: block; }
  html body #cycle .principles-terms { border-left: 0; padding-left: 0; margin-top: var(--s5); }
  html body #cycle .cycle-list { max-width: none; }
  html body #cycle .cycle-note { max-width: none; margin-left: 0; text-align: left; }
  html body #students .students-inner, html body #pilot .pilot-inner > p:not(.section-index) { margin-left: 0; max-width: none; padding-top: 0; }
  html body #pilot .pilot-inner > .button { margin-left: 0; }
}

/* THE HALO FOLLOWS THE BLOCK'S SHAPE. One fixed geometry cannot serve both a 700 px paragraph and a 1120 px run:
   measured after the layouts changed, the run's tail and the right-hand argument's last lines fell outside their
   halos and sat on the raw scene at 3.03:1 and 3.96:1 (qa/r106i-glyph.cjs). The run gets a wide, nearly even
   wash; a block that stands on its own gets a halo centred on itself rather than on the left of the text zone. */
html body #cycle .cycle-list.scrim::before {
  inset: -14% -9% -14% -11%;
  background: linear-gradient(95deg, rgba(4, 11, 26, .58) 0%, rgba(4, 11, 26, .54) 52%,
    rgba(4, 11, 26, .46) 82%, rgba(4, 11, 26, 0) 100%); }
html body #students .students-inner.scrim::before,
html body #pilot .pilot-inner.scrim::before,
html body #cycle .cycle-note.scrim::before,
html body #cycle .principles.scrim::before,
html body #meaning .team-lede.scrim::before {
  inset: -18% -20% -18% -22%;
  background: radial-gradient(88% 120% at 50% 50%, rgba(4, 11, 26, .62) 0%, rgba(4, 11, 26, .48) 50%,
    rgba(4, 11, 26, .18) 78%, rgba(4, 11, 26, 0) 100%); }




