/* tauthinking.com — marketing only.
 *
 * tokens.css is a copy of the product's design system (see the header there).
 * components.css is deliberately NOT copied: it holds score chips, transcript
 * turns and meters, none of which a landing page has. Everything below is
 * marketing chrome — hero, section rhythm, split rows, nav — written
 * against --tau-* tokens so the page reads as the same product.
 *
 * TWO DELIBERATE EXTENSIONS, both scoped to this file and marketing only.
 * designsystem.md was written for the app and doesn't cover a landing page,
 * so these are flagged as gaps rather than pretended to be locked decisions:
 *
 *   1. --m-text-* below. tokens.css §7 is a UI type scale — 15.5px body is
 *      right for a dense dashboard and undersized for marketing prose read at
 *      arm's length. This is a second scale for this page, not a change to the
 *      product's.
 *   2. .band-forest. designsystem.md gives forest exactly six jobs and
 *      "marketing section ground" isn't one. Used exactly TWICE on this page,
 *      as bookends: the hook the page opens on ("Beyond the assignment") and
 *      the claim it closes on ("Where the teaching happens"). Two dark bands
 *      top and tailing a near-white scroll read as rhythm; a third would make
 *      forest the page's ground rather than its emphasis, so this is the
 *      ceiling, not a precedent. Never carries a score, a status, or a
 *      student's data — only a claim.
 *
 * Neither belongs back in the app.
 */

:root {
  --m-text-body:  18px;
  --m-text-lead:  20px;
  --m-measure:    62ch;
  --m-page:       1080px;   /* one column, everything in it */
}

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

/* The nav's links are same-page anchors, so a click jump-cuts the reader to a
   heading with no sense of how far they travelled or what they passed. Smooth
   scrolling restores that on a page whose whole argument is its sequence.
   CSS rather than a scroll handler: tokens.css already forces scroll-behavior
   back to auto under prefers-reduced-motion, so the accessibility case is
   answered by a rule that is already there.

   scroll-padding-top clears the 64px sticky nav. Without it the anchor lands
   flush with the viewport top and the heading sits UNDER the bar — a bug the
   instant jump has today, and one smooth scrolling would only make easier to
   watch happen. */
html {
  scroll-behavior: smooth;
  scroll-padding-top: 88px;
}

body {
  margin: 0;
  background: var(--tau-bg);
  color: var(--tau-ink);
  font-family: var(--tau-font-ui);
  font-size: var(--m-text-body);
  line-height: var(--tau-leading-body);
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; display: block; }
a { color: var(--tau-forest); }

.wrap {
  width: 100%;
  max-width: var(--m-page);
  margin: 0 auto;
  padding: 0 var(--tau-s5);
}

.skip-link { position: absolute; left: -9999px; }
.skip-link:focus {
  left: var(--tau-s4);
  top: var(--tau-s4);
  z-index: 20;
  background: var(--tau-surface);
  padding: var(--tau-s3) var(--tau-s4);
  border-radius: var(--tau-r-md);
}

/* ---------- nav ---------- */

.nav {
  position: sticky;
  top: 0;
  z-index: 10;
  background: color-mix(in oklab, var(--tau-bg) 88%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid transparent;
  transition: border-color var(--tau-dur-short) var(--tau-ease-standard);
}
.nav.scrolled { border-bottom-color: var(--tau-line); }

/* Scroll position, on a page that's now a sequence of chapters. scaleX only —
   animating width would lay out on every frame. */
.nav-progress {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 2px;
  background: var(--tau-forest);
  transform: scaleX(0);
  transform-origin: 0 50%;
}

.nav-inner {
  display: flex;
  align-items: center;
  gap: var(--tau-s4);
  height: 64px;
}

/* logo-mark.png is itself a TAU wordmark, so the adjacent word is "Thinking"
   only — "Tau Thinking" beside it renders the name twice. */
.nav-mark {
  display: flex;
  align-items: center;
  gap: var(--tau-s2);
  font-weight: 700;
  font-size: var(--tau-text-md);
  letter-spacing: -0.01em;
  color: var(--tau-ink);
  text-decoration: none;
  margin-right: auto;
}
.nav-logo { height: 24px; width: auto; }

/* logo-mark.png is a flat forest-green PNG with no dark variant, so on the
   charcoal ground it sits at roughly the contrast of body text — legible, but
   not a wordmark. Lifting it to about the mint that --tau-forest already
   resolves to in dark theme keeps the brand hue instead of flattening it to
   white. STOPGAP: the app has the same gap on all five of its pages. The real
   fix is a second asset, which belongs in the app repo, not here. */
:root[data-theme="dark"] .nav-logo { filter: brightness(1.8) saturate(1.1); }

.nav-links {
  display: flex;
  align-items: center;
  gap: var(--tau-s5);
  font-size: var(--tau-text-sm);
}
.nav-links a { color: var(--tau-ink-soft); text-decoration: none; }
.nav-links a:hover { color: var(--tau-forest); }

@media (max-width: 860px) {
  .nav-links { display: none; }
}

/* ---------- buttons ---------- */

.mbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tau-target);
  padding: 0 var(--tau-s6);
  border-radius: var(--tau-r-pill);
  border: 1px solid transparent;
  font: inherit;
  font-size: var(--tau-text-md);
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--tau-dur-short) var(--tau-ease-standard),
              color var(--tau-dur-short) var(--tau-ease-standard),
              border-color var(--tau-dur-short) var(--tau-ease-standard);
}
.mbtn-primary { background: var(--tau-forest); color: var(--tau-on-forest); }
.mbtn-primary:hover { background: var(--tau-forest-lift); }

.mbtn-secondary {
  background: var(--tau-surface);
  color: var(--tau-forest);
  border-color: var(--tau-line-strong);
}
.mbtn-secondary:hover { border-color: var(--tau-forest); }

.mbtn-sm { min-height: 38px; padding: 0 var(--tau-s5); font-size: var(--tau-text-sm); }

/* On the forest band, the primary fill would vanish into the ground. */
.band-forest .mbtn-primary {
  background: var(--tau-on-forest);
  color: var(--tau-forest);
}
.band-forest .mbtn-primary:hover { background: var(--tau-on-forest); opacity: 0.9; }

/* ---------- hero ---------- */

.hero { padding: clamp(48px, 9vw, 104px) 0 0; }

/* Two columns: the brand block holds the left rail, the claim holds the right.
   Top-aligned rather than centred — a mark floating at the vertical midpoint of
   a tall text block reads as unmoored, and the eye expects the wordmark and the
   headline to start on the same line. */
.hero-grid {
  display: grid;
  /* min-content, not a pixel floor: the lockup's span is nowrap, so its
     min-content IS the width of "Transformative AI Use" set on one line. The
     column can never be narrower than that, at any font size the clamp lands
     on, so the name cannot break across two lines. */
  grid-template-columns: minmax(min-content, 260px) 1fr;
  gap: clamp(var(--tau-s7), 6vw, var(--tau-s10));
  align-items: start;
}

/* logo-hero.png is the TAU wordmark itself, so the adjacent word is "Thinking"
   only — "Tau Thinking" beside it sets the name twice. Stacked here rather than
   inline: in a narrow rail the two read as one block, and the mark gets to be
   the size a hero deserves. */
.hero-lockup {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--tau-s3);
  margin: 0;
  /* Optical, not mechanical: aligns the mark's cap height with the h1's rather
     than its box with the h1's box. */
  padding-top: clamp(4px, 1vw, 12px);
}
.hero-lockup img {
  width: 100%;
  max-width: 240px;
  height: auto;
}
.hero-lockup span {
  font-size: clamp(19px, 2.1vw, 26px);
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--tau-ink);
  white-space: nowrap;
}

@media (max-width: 860px) {
  .hero-grid { grid-template-columns: 1fr; gap: var(--tau-s6); }
  .hero-lockup { flex-direction: row; align-items: baseline; gap: var(--tau-s3); padding-top: 0; }
  .hero-lockup img { width: auto; height: clamp(34px, 8vw, 46px); }
  /* Still one line, now sharing a row with the mark: sized off the viewport so
     the pair fits at 360px rather than overflowing. */
  .hero-lockup span { font-size: clamp(15px, 4vw, 26px); }
}
:root[data-theme="dark"] .hero-lockup img { filter: brightness(1.8) saturate(1.1); }

.hero h1 {
  margin: 0;
  font-size: clamp(42px, 8vw, 78px);
  line-height: 1.02;
  letter-spacing: -0.035em;
  font-weight: 700;
  max-width: 14ch;
}
.hero-sub {
  margin: var(--tau-s5) 0 0;
  font-size: clamp(20px, 2.8vw, 28px);
  line-height: var(--tau-leading-tight);
  letter-spacing: -0.01em;
  color: var(--tau-forest);
  font-weight: 500;
  max-width: 24ch;
}
.hero-body {
  margin: var(--tau-s6) 0 0;
  max-width: 54ch;
  font-size: var(--m-text-lead);
  color: var(--tau-ink-soft);
}
.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--tau-s3);
  margin-top: var(--tau-s7);
}
.hero-note {
  margin: var(--tau-s4) 0 0;
  font-size: var(--tau-text-sm);
  color: var(--tau-ink-faint);
}

.hero { padding-bottom: clamp(48px, 8vw, 88px); }

/* ---------- the stage ---------- */

/* ONE TONE MEANS "HERE IS THE PRODUCT."
 *
 * Every screenshot on this page sits on .stage, and nothing else ever does.
 * A shared background is a stronger grouping signal than a border (Gestalt
 * common region), so the tone itself announces the screenshots — the reader
 * learns the rule by the second one without being told.
 *
 * The band runs edge to edge while its content stays in .wrap/.wrap-wide;
 * that full-bleed change of ground is what segments a long scroll into
 * chapters. It carries no border: the tone change already separates the
 * sections, and doubling the two is the same "one signal, stated twice"
 * failure designsystem.md forbids on a chip.
 */
/* GROUND IS A SEPARATE DECISION FROM LAYOUT.
 *
 * .stage used to carry the sage ground itself, so every showcase section got
 * the same tint and a long run of them read as one band with figures loose
 * inside it. The ground moves out to .tint, and sections now alternate.
 *
 * Two channels, used ALTERNATELY rather than together: a tinted section holds
 * its figures flat, a plain section holds them elevated on white. Stacking both
 * — a tinted ground under a lifted white card that also breaks the column — is
 * what made the turnplot look like it was escaping its section rather than
 * being the subject of one.
 *
 * The rhythm down the page, each chapter being prose plus the figure it
 * explains, and no two adjacent chapters sharing a ground:
 *   essay (plain) → report (tint) → dimensions (plain) → evidence (tint)
 *   → teaching (plain) → every class (tint) → patterns (plain)
 *   → one class (tint) → movement (plain)
 */
.stage { padding: clamp(44px, 7vw, 88px) 0; }
.tint  { background: var(--tau-surface-2); }

/* --- .shell — the ground a .home-band is raised off, ported with the app's
   2026-08-22 elevation pass (app commit a6fda25).

   A band is --tau-surface-2. On the page's plain ground (--tau-bg, 0.992) that
   is a ~2.4% lightness step in the RECESSED direction, because light descends
   from white while dark ascends from near-black — so the same declaration read
   raised in dark and sunken in light, and adding a shadow lit an inset rather
   than fixing it. --tau-shell sits below the ramp (0.920) and buys a ~10% step,
   which is what makes the band read raised and the shadow read as a shadow.

   Applied to the two stage sections that host a .home-band, and nowhere else:
   sections whose content is prose or white cards keep --tau-bg. .stage's seam
   rules already zero the border wherever the ground changes, and .shell is a
   ground change, so no seam rule needs to know about it.

   Two of the app's forced consequences do not arise here — this page draws no
   divider on the ground, and its only faint text inside these sections sits
   within the band, not on the shell (ink-faint measures 2.79:1 there). Check
   both again before putting anything new directly on this ground. */
.shell { background: var(--tau-shell); }

.stage + .section { border-top: 0; }
.section + .stage { border-top: 0; }
/* A hairline is only needed where two sections share a ground. Where the
   ground changes, the tone change already separates them and the line reads as
   a seam — the same "one signal, stated twice" failure forbidden on a chip. */
.tint + .tint, .section:not(.tint) + .section:not(.tint) { border-top: 1px solid var(--tau-line); }
.tint + .section:not(.tint), .section:not(.tint) + .tint,
.stage + .tint, .tint + .stage { border-top: 0; }

/* Two STAGES sharing a ground, which the ordering of 2026-08-23 created in both
   directions: the class overview moved up beside the triage row (tint + tint),
   and that left patterns beside movement (shell + shell). `.stage + .tint` above
   zeroes the first pair, so these need the extra specificity to win — without
   them each pair runs together as one long field, which is the exact thing the
   hairline rule exists to prevent.

   line-strong on the shell, not line: --tau-line is LIGHTER than --tau-shell and
   inverts into an emboss there. Same forced consequence the app hit when it moved
   its own dividers onto the shell ground. */
.stage.tint + .stage.tint { border-top: 1px solid var(--tau-line); }
.stage.shell + .stage.shell { border-top: 1px solid var(--tau-line-strong); }
/* .close carries the tinted ground too, so the same rule applies to it. */
.section:not(.tint) + .close, .stage:not(.tint) + .close { border-top: 0; }


/* Figures on a plain ground carry their own elevation; on a tinted ground the
   ground is already doing that work, so they sit flat against it. */
.stage:not(.tint) .turnplot-card,
.stage:not(.tint) .dim-card { box-shadow: var(--tau-shadow); }
.tint .turnplot-card { box-shadow: none; }

/* ---------- product fragments ---------- */

/* These rows render the app's OWN molecules — .dim-grid, .stat-tile, .chip-*,
   .card — from components.css, rather than a downscaled PNG of
   them. Everything below is layout around those components: margins, stacking,
   the grid a trend row sits on. Per the design system's own rule, a page sheet
   may lay a component out and may never redefine its chrome, so nothing here
   touches a component's background, border, radius, padding or shadow.
   The strings are real values from the seeded demo data.

   THE TRADE: a fragment is a claim about a UI, and unlike a screenshot it
   never looks stale when the product moves on. If the app's markup changes,
   these have to be re-checked by hand. */



/* ---------- organisms, transcribed ---------- */

/* WHY THIS BLOCK EXISTS.
 *
 * components.css gives us the app's ATOMS and MOLECULES. It does not give us
 * the ORGANISMS — the assembled blocks a teacher actually looks at. Those live
 * in `dashboard.html`'s inline <style> and in `report.css`, neither of which is
 * copied here: designsystem.md keeps the dashboard's block inline by design,
 * and report.css is a page sheet.
 *
 * The first attempt improvised markup against whichever molecule name looked
 * closest, and got several of them wrong outright — the coaching note is
 * .teach-note, not .teaching-card. Two of the three corrections have since
 * expired, in opposite directions: "the class idea mix is .origin-pill" is moot
 * because the mix itself is gone (see the .origin-* note below), and "the trend
 * chips are .dim-badge, not .chip-*" was right until the app deleted the surface
 * that rendered .dim-badge. A correction is only true against a given version.
 *
 * So: every rule below is transcribed VERBATIM from the app, with its source.
 * Do not edit them to taste — that is the whole point. Re-check them when the
 * app changes; they cannot arrive by `cp` the way tokens and components do.
 *
 * Source: app/web/dashboard.html <style>, lines ~473–660  (2026-08-07)
 */

.agg-stat-row { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: var(--tau-s5); }
/* .agg-block-lbl was HERE and is now deleted, not updated. The app moved it into
   components.css and put it on the label role (sentence case, --tau-text-label-md)
   in the 2026-08-22 pass that retired eight uppercase treatments. This sheet loads
   after components.css, so the stale copy silently won and the label kept its caps
   through a `cp` that had already fixed it. When the app moves a rule into
   components.css, DELETE the transcription — do not re-transcribe it. */

.draft-stat { flex: 1; min-width: 120px; border: 1px solid var(--tau-line); border-radius: var(--tau-r-sm); padding: 9px 11px; background: var(--tau-surface-2); }
.draft-stat-val { font-size: var(--tau-text-lg); font-weight: 700; font-variant-numeric: tabular-nums; }
.draft-stat-label { font-size: 11px; color: var(--tau-ink-soft); margin-top: 1px; }

/* GEOMETRY REVISED 2026-08-22 — a card like every other card: radius on all
   four corners, no left rule. The stripe was drawn when this note was an accent
   inside a white sheet and needed an edge to belong to; it is now a standalone
   card, where a squared-off left side reads as a fragment of something rather
   than an object. The VOICE is unchanged and still double-encoded — tool-info
   tint plus the mono blue eyebrow — so nothing was lost with the stripe except
   the alignment break. */
.teach-note { padding: var(--tau-s3) var(--tau-s4); border-radius: var(--tau-r-md); background: var(--tau-tool-info-bg); border: 1px solid transparent; box-shadow: var(--tau-shadow); font-size: var(--tau-text-xs); color: var(--tau-ink); }
.teach-note.stacked { margin-bottom: var(--tau-s5); }
/* Caps SURVIVE here — mono + uppercase is the tool-info voice marker, the same
   exemption .turn-speaker holds. Only the size moved onto the label role. */
.teach-note .eyebrow-lbl { display: flex; align-items: center; gap: 6px; font-size: var(--tau-text-label-sm); font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--tau-tool-info); font-family: var(--tau-font-mono); margin-bottom: 6px; }
.teach-note .eyebrow-lbl .tau-icon { width: 13px; height: 13px; flex-shrink: 0; }
.teach-note + .teach-note { margin-top: var(--tau-s3); }
.note-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 7px; }
.note-list li { padding-left: 13px; position: relative; }
.note-list li::before { content: "–"; position: absolute; left: 0; color: var(--tau-tool-info); }

/* .origin-mix / .origin-pill / .origin-dot WERE HERE — the pooled class idea
   mix ("Student-born 36%") — and they are gone rather than unused, 2026-08-23.

   The app removed the same block from the assignment aggregate and wrote down
   why: OC's cell in the grid IS student-originated material observed in the
   essay, so the pills counted tags to say a thing the dimension bands already
   say against a coded rubric; a pooled percentage carries no threshold, no
   target and no names, so it routes nowhere, and its denominator was concepts
   rather than students. The app left the class-overview copy standing as "a
   separate decision, deliberately not made here" — that decision is made here.

   Per-submission provenance is untouched and is a different thing: it reads
   honestly where the denominator is ONE essay and the concepts sit next to
   their phrases. If a whole-class version returns it comes back as a PATTERN
   ("AI-originated ideas — 6 students"), which routes, and not as a ratio. */

/* .dim-panel / .dim-row / .dim-badge WERE HERE, and they are gone rather than
   unused. The app deleted the surface that rendered them: a per-dimension
   "Trending up / Flat (ceiling)" table classified an average of ordinals, which
   is the same arithmetic the 4–20 total was retired for. Its CSS still sits in
   dashboard.html (lines 373–379) with no caller anywhere in the file — dead
   there, and a lie here. The question it answered ("did the group move?") is
   now the Movement flow further down this page.
   Removed 2026-08-19. Do not restore from the app's stylesheet: the presence of
   a rule is not evidence that the product still renders it. */


/* Source: app/web/dashboard.html <style>, lines 683–724, 742–786, 937–942
 *                                                              (2026-08-19)
 *
 * HOME — the surface a teacher lands on, across every class they teach. Its
 * atoms and molecules arrive by `cp` in components.css (.home-cols-top,
 * .stat-row-count, .pattern-group-label, .pattern-body, .pattern-try,
 * .card-tabs/.card-tab, .section-head, .viz-card, .trend-lede, every .flow-*
 * rule and the .dbar-key legend). What follows is only what dashboard.html
 * keeps in its own page sheet, which `cp` cannot reach.
 *
 * The band is the grouping device: one toned .home-band per SECTION, never one
 * shared band around related sections — a region asserts its contents belong
 * together, and triage, patterns and movement do not.
 *
 * Cards expand, rows navigate. That split is load-bearing: .pattern-card is a
 * <details> that opens in place, .class-row is a forest-coloured row with a
 * trailing → that goes somewhere. Same chrome for both behaviours is the
 * affordance failure the app fixed by giving them different chrome.
 */
.finding-list { display: flex; flex-direction: column; gap: 10px; }
.pattern-card { background: var(--tau-surface); border: 1px solid var(--tau-line); border-radius: var(--tau-r-md); box-shadow: var(--tau-shadow); overflow: hidden; }
.finding-summary {
  display: flex; align-items: center; gap: 14px;
  padding: var(--tau-s3) var(--tau-s5); min-height: var(--tau-target);
  cursor: pointer; list-style: none;
  transition: background var(--tau-dur-short) var(--tau-ease-standard);
}
.finding-summary::-webkit-details-marker { display: none; }
.finding-summary:hover { background: var(--tau-hover-surface); }
.finding-summary:focus-visible { outline: var(--tau-focus-ring); outline-offset: calc(-1 * var(--tau-focus-gap)); }
.f-body { flex: 1; min-width: 0; }
.pattern-title { font-size: 14.5px; font-weight: 700; display: block; }
/* The count never stands alone — it sits in the phrase that names its unit
   ("3 students"), weighted and coloured inside that sentence so it still
   reads first. A bare numeral in a column is a number the reader decodes. */
.f-where { font-size: 12px; color: var(--tau-ink-soft); margin-top: 3px; display: block; }
.f-where .num { color: var(--tau-caution); font-weight: 700; font-variant-numeric: tabular-nums; }
.f-where.attn .num { color: var(--tau-attention); }
.f-where.neutral .num { color: var(--tau-ink); }
.f-detail { padding: var(--tau-s4) var(--tau-s5); border-top: 1px solid var(--tau-line); }
.f-detail > :last-child { margin-bottom: 0; }
.pattern-link { display: inline-block; font-size: 12.5px; font-weight: 600; color: var(--tau-forest); cursor: pointer; }

/* No hairline: the tint carries the edge, and a border on top of a tinted fill
   AND a shadow is three channels doing one job. --tau-shadow rather than -lift
   because these are peers down the page, not one takeaway. The shadow only
   works because the band sits on .shell — see the note there. */
.home-band {
  background: var(--tau-surface-2);
  border-radius: var(--tau-r-lg);
  padding: var(--tau-s5);
  box-shadow: var(--tau-shadow);
}
/* A child must not float above its own parent. Inside the band the pattern
   cards are white on sage — the strongest edge on the screen — so they need no
   shadow of their own, and keeping one made them read as more elevated than
   the band containing them. Scoped to the band, not removed from .pattern-card. */
.home-band .pattern-card { box-shadow: none; }
.home-stack { display: flex; flex-direction: column; gap: var(--tau-s6); }
.home-col { min-width: 0; }
/* A white card hovering to surface-2 would dissolve into the band underneath
   it. Darken instead — the same direction .rail-item:hover already takes on
   this exact ground. Scoped to the band so surfaces without one keep theirs. */
.home-band .finding-summary:hover { background: var(--tau-surface-3); }

.class-row {
  display: flex; align-items: center; gap: var(--tau-s3);
  min-height: var(--tau-target); padding: 0 var(--tau-s2);
  border-bottom: 1px solid var(--tau-line); border-radius: var(--tau-r-sm);
  cursor: pointer;
  transition: background var(--tau-dur-short) var(--tau-ease-standard);
}
.class-row:last-child { border-bottom: 0; }
/* The group label draws its own full-width rule, so the row above it must not
   draw the between-rows one as well — two stacked lines read as a mistake. */
.class-row:has(+ .stat-group-lbl) { border-bottom: 0; }
.class-row:hover { background: var(--tau-surface-3); }
.class-row-name { flex: 1; min-width: 0; font-size: var(--tau-text-sm); font-weight: 600; color: var(--tau-forest); }
.class-row-go {
  flex-shrink: 0; color: var(--tau-ink-faint); font-size: 13px; line-height: 1;
  transition: transform var(--tau-dur-short) var(--tau-ease-standard);
}
.class-row:hover .class-row-go { transform: translateX(2px); color: var(--tau-forest); }

/* Groups the two "we have no reading on this student" rows under the one
   tile card. A label rather than a second card: they are the same follow-up
   list, split by whether the gap is the student's thinking or our sight of
   it. */
.stat-group-lbl {
  font-size: 10px; font-weight: 700; letter-spacing: var(--tau-track-label);
  text-transform: uppercase; color: var(--tau-ink-faint);
  margin: var(--tau-s6) 0 var(--tau-s1); padding-top: var(--tau-s4);
  border-top: 1px solid var(--tau-line);
}

/* THE FIRST DEVIATION, and it is behaviour only. On the dashboard these rows
   and links are controls; here nothing on the page navigates anywhere, so the
   pointer cursor and the hover shift would promise a destination this page does
   not have. Chrome, type and colour are untouched — a page sheet may not
   redefine a component, only stop it lying about what it does.
   Unscoped, because there is no navigating row anywhere on this page. */
.class-row { cursor: default; }
.class-row:hover { background: none; }
.class-row:hover .class-row-go { transform: none; color: var(--tau-ink-faint); }
.pattern-link { cursor: default; }

/* THE SECOND, and it is a substitution rather than an override: the triage and
   class lists are laid out in .card.card-lg, where the app puts them in
   .home-band. Not a preference — a band is surface-2 lifted off a WHITE
   dashboard pane, and the section it lands in here is tinted, so band and
   ground are the same colour and the two halves ran together as one field of
   rows. The white card does the separating instead, and it is the app's own
   card component, not a band repainted.

   The rule this follows, rather than breaks: ground is chosen per section, and
   a component that depends on the ground it sits on has to be re-picked when
   the ground changes. .home-band survives one section down, where the patterns
   list sits on a plain ground and a band still reads as a band.

   Layout only, and only what .home-band was already setting: the card arrives
   with its own padding and radius from components.css. */
.home-cols-top > .card { gap: var(--tau-s4); }
/* A class row can carry two chips and a name; neither may squeeze the other. */
.signal-pill-review { flex-shrink: 0; white-space: nowrap; }
/* Layout only. The class rows hold a name plus two chips, which is more than a
   dashboard column ever asks of them — below this they wrap rather than
   compressing the name to nothing. Chrome untouched. */
@media (max-width: 560px) {
  .home-band .class-row { flex-wrap: wrap; padding: var(--tau-s2); }
  .home-band .class-row-name { flex-basis: 100%; }
}


/* Source: app/web/report.css, lines ~993–1076, 1102–1215  (2026-08-19)
 *
 * The report hero — the four level names and the reading panel beside them.
 * The card itself (.card, .card-lg, .card-hero) and .eyebrow arrive by `cp` in
 * components.css; everything below is report.css, which does not.
 *
 * Rebuilt 2026-08-24 with the app: the vertical .level-scale rail and its
 * .hero-two-col wrapper are gone, along with .hero-scale-col and
 * .hero-scale-foot. See the .level-block note below for why.
 *
 * NOT transcribed: report.css's .dim-quad-callout override (it redefines a
 * shared component, which a page sheet may not do — the app's own comment
 * marks it as staying in the lab).
 */

/* The assignment names the report, so it is set as a title rather than as half
   of a faint uppercase crumb — it was the smallest, palest text in the card
   while being the one fact that says which report this is. Sentence case is the
   point, not a side effect: uppercase with tracking flattens word shape, which
   is what a reader scans a proper noun by.

   .eyebrow keeps its uppercase and keeps the draft, which is a position rather
   than a name. One size below .level-def so the reading still leads the card
   and the title only identifies it.

   The app sets the title as the report's <h1>. Here it is a <p>: this is a
   fragment quoted inside a marketing page that already has its own heading
   order, and a stray h1 in the middle of it is an outline bug, not fidelity. */
.report-hero-head {
  display: flex;
  align-items: baseline;
  gap: var(--tau-s3);
  flex-wrap: wrap;
}
.report-hero-title {
  margin: 0;
  font-size: var(--tau-text-xl);
  font-weight: 600;
  line-height: var(--tau-leading-tight);
  color: var(--tau-ink);
}
.report-hero-eyebrow { color: var(--tau-ink-faint); margin: 0; }

/* --- .level-block — the four on the left, the reading on the right.
   Two columns. The left holds the four level names in their fixed order,
   strongest at the top, with a highlight band on the one that was read; the
   right is the reading itself — definition, this session's description and
   the exception — in one panel taking the band's fill.

   THE CARD'S HEIGHT DOES NOT MOVE. Nothing expands and no row grows, so the
   block is the same shape whatever the level.

   NO RAIL AND NO DOTS. That is the whole difference between this and the
   ladder it replaced on 2026-08-24: a rail with a marker on it is an axis,
   and a position on an axis reads as a mark out of four whether or not a
   numeral is printed — the failure mode that retired the SAMR vocabulary and
   then got rebuilt in geometry. A highlighted name beside a panel is a label,
   not a coordinate. The --level-bg / --level-fg pair is set inline on
   .level-block from the band ramp, so one property change recolours the band
   and the panel together. */
.level-block {
  display: grid;
  grid-template-columns: minmax(0, 13rem) minmax(0, 1fr);
  gap: var(--tau-s5);
  align-items: stretch;
}

.level-names { display: flex; flex-direction: column; gap: var(--tau-s1); }
.level-name {
  padding: var(--tau-s3) var(--tau-s4);
  border-radius: var(--tau-r-md);
  font-size: var(--tau-text-lg);
  font-weight: 500;
  line-height: var(--tau-leading-snug);
  color: var(--tau-ink-faint);
}
/* The band ramp's own bg/fg pairing. Never a positive/caution pair: semantic
   colour never touches a student's own score, because a level is a position
   on a path and not a verdict. */
.level-name.is-here { background: var(--level-bg); color: var(--level-fg); font-weight: 650; }

/* One panel holds the general claim, the particular one and the
   counterexample. The exception is INSIDE it deliberately — left on the white
   below a tinted panel it read as an afterthought, and its slot is fixed
   precisely so it cannot be dropped when a session goes well. */
.level-statement {
  background: var(--level-bg);
  border-radius: var(--tau-r-md);
  padding: var(--tau-s6) var(--tau-s7);
  display: flex;
  flex-direction: column;
  gap: var(--tau-s5);
  min-width: 0;
}
/* The level's NAME is not part of this sentence — the left column names it,
   and repeating it would be the card saying it twice.

   60ch, not the 46ch it carried while the panel was the whole hero: this is a
   headline over the two columns below it, so it should break past their
   gutter rather than well short of it.

   `pretty` not `balance`: balance splits a headline that runs just over one
   line into two half-width lines, which is a ragged break by another route. */
.level-def {
  margin: 0;
  max-width: 60ch;
  font-size: var(--tau-text-2xl);
  line-height: 1.3;
  color: var(--tau-ink);
  text-wrap: pretty;
}
/* The description and the counterexample side by side. Stacked in one column
   they leave the panel's right half empty and make the counterexample look
   like an indented afterthought — as a second column it reads as the other
   half of the same statement, which is what it is. */
.level-statement-body { display: grid; grid-template-columns: 1fr 1fr; gap: var(--tau-s5) var(--tau-s7); align-items: start; }
.level-statement .level-body { max-width: 62ch; font-size: var(--tau-text-lg); line-height: var(--tau-leading-snug); color: var(--tau-ink-soft); }
/* The hero's exception keeps the LABEL half of the set-apart device and drops
   the hairline: that rule was drawn to separate it from a description it sat
   beneath on white, and inside the panel, where the two are side-by-side
   columns, it is furniture doing a job the panel's edge already does. The
   dimension cards below still sit on white and keep it. */
.level-statement .aside { max-width: 62ch; }
.level-statement .aside::before { display: none; }
@media (max-width: 880px) { .level-statement-body { grid-template-columns: 1fr; } }

/* The one thing the four names cannot say for themselves: a reader sees an
   ordered set with one of them marked and no reason to read it as anything
   but a mark out of four. It sits OUTSIDE the panel — it is a standing
   condition on how to read the card, not part of this session's statement,
   and --tau-ink-faint is cleared for contrast on the surfaces, not on a band
   fill. */
.hero-level-foot {
  margin: var(--tau-s4) 0 0;
  max-width: 62ch;
  font-size: var(--tau-text-sm);
  line-height: var(--tau-leading-snug);
  color: var(--tau-ink-faint);
}

/* Layout only, the one move a page sheet gets on a component: the 13rem
   column plus prose overflows a phone. Widths and stacking, no chrome. */
@media (max-width: 620px) {
  .level-block { grid-template-columns: 1fr; }
}

/* Source: app/web/report.css, lines ~770–820, 903  (2026-08-19)
 *
 * One reading, one card. This replaced the 2x2 .dim-grid of .dim-quads in the
 * app — report.css:751: the quads shared row boxes, so opening one silently
 * reshaped its row-mate. Four independent cards have no shared geometry.
 *
 * The cards are interactive here exactly as they are in the product: click or
 * Enter/Space opens the reading in a container transform, Escape closes it,
 * focus moves in and returns to the card it came from. The .xform machinery
 * below and its script in index.html are both transcribed from the app.
 */

/* The app uses `align-items: start`, so each card is only as tall as its own
   content. Here the four sit side by side as a single exhibit and the ragged
   bottoms read as a rendering fault rather than as four independent readings,
   so the rows are equalised instead. Layout only. */
/* .readings is also the delegation root the xform script binds to — the app
   renders its cards into it, so it is where the click and keydown listeners
   live. Keep the wrapper even though it holds one child here. */
.readings { display: flex; flex-direction: column; gap: var(--tau-s3); }
.dim-cards { display: grid; grid-template-columns: 1fr 1fr; grid-auto-rows: 1fr; gap: var(--tau-s4); align-items: stretch; }
.dim-card { display: block; padding: 0; cursor: pointer; transition: box-shadow var(--tau-dur-short) var(--tau-ease-standard); }
/* Deviation from the app, which tints the card with --tau-hover-surface. A
   lift reads as "this comes forward" rather than "this is selected", and it
   is the same --tau-shadow-lift the surface itself settles on once open — so
   the hover is the first frame of the transform rather than a separate idea. */
.dim-card:hover { box-shadow: var(--tau-shadow-lift); }
.dim-card:focus-visible { outline: var(--tau-focus-ring); outline-offset: var(--tau-focus-gap); }
/* The card the surface grew out of, hidden while the surface stands in for it. */
.dim-card.is-source { opacity: 0; }
/* height:100% + margin-top:auto on the invitation so that, with the rows
   equalised above, the "moments behind this" line sits on the card's bottom
   edge in all four rather than floating under short callouts. */
.dim-card-head { padding: var(--tau-s5); display: flex; flex-direction: column; gap: var(--tau-s3); height: 100%; }
.dim-card-head .dim-card-more { margin-top: auto; }
.dim-card-q { font-family: var(--tau-font-serif); font-size: var(--tau-text-xl); font-weight: 600; line-height: var(--tau-leading-tight); }
/* The app pairs this label with a .dim-card-chev caret. Dropped here: the
   card opens into a centred surface, and a downward caret promises an
   accordion. The label carries the affordance on its own. */
.dim-card-more { display: inline-flex; align-items: center; gap: var(--tau-s2); font-size: var(--tau-text-sm); font-weight: 600; color: var(--tau-forest); }

/* report.css strips the callout's left bar so the card reads as one decision
   rather than two. Scoped to .dim-card here rather than applied bare: the app
   sets it globally on its own page sheet, but on this page .dim-quad-callout
   is a shared component and a page sheet may not redefine one outright.
   .xform is listed too: the surface is built from the card's innerHTML but is
   not itself inside .dim-card, so a .dim-card-only scope let the bar reappear
   the moment a card was opened. */
.dim-card .dim-quad-callout,
.xform .dim-quad-callout { border-left: 0; padding-left: 0; font-size: var(--tau-text-md); }

@media (max-width: 880px) { .dim-cards { grid-template-columns: 1fr; } }

/* Source: app/web/report.css, lines ~826–895, 1041–1048  (2026-08-19)
 *
 * The container transform. The card does not grow in place — report.css:751
 * records why: evidence is prose and quotes, and half a grid column is the
 * wrong measure for it. The card becomes a centred surface instead, animating
 * from its own box so the two read as one object rather than as a card that
 * vanished and a dialog that appeared. */

.dim-card-body { display: none; }

.xform-scrim {
  position: fixed; inset: 0; z-index: 40;
  background: var(--tau-scrim);
  opacity: 0;
  transition: opacity var(--tau-dur-medium) var(--tau-ease-standard);
}
.xform-scrim.is-open { opacity: 1; }

.xform {
  position: fixed; z-index: 41;
  background: var(--tau-surface);
  border: 1px solid var(--tau-line);
  overflow: hidden;
  box-shadow: var(--tau-shadow);
  transition:
    left   var(--tau-dur-medium) var(--tau-ease-decelerate),
    top    var(--tau-dur-medium) var(--tau-ease-decelerate),
    width  var(--tau-dur-medium) var(--tau-ease-decelerate),
    height var(--tau-dur-medium) var(--tau-ease-decelerate),
    border-radius var(--tau-dur-medium) var(--tau-ease-decelerate),
    box-shadow var(--tau-dur-medium) var(--tau-ease-standard);
}
.xform.is-open { box-shadow: var(--tau-shadow-lift); }
/* Leaving accelerates — a return is not the outbound curve played backwards. */
.xform.is-closing { transition-timing-function: var(--tau-ease-accelerate); }

/* Carried into the surface unchanged: this is the part that does NOT
   cross-fade, and it is what makes the two read as one object. */
.xform .dim-card-head { padding: var(--tau-s6); height: auto; }
/* Inside the surface the moments are already open, so the invitation would be
   an instruction to do the thing you just did. */
.xform .dim-card-more { display: none; }

/* The part that does cross-fade, held back until the surface has nearly
   finished moving so the text is not readable mid-flight. */
.xform .dim-card-body {
  display: flex;
  flex-direction: column;
  gap: var(--tau-s5);
  padding: 0 var(--tau-s6) var(--tau-s6);
  opacity: 0;
  transform: translateY(6px);
  transition: opacity var(--tau-dur-short) var(--tau-ease-standard),
              transform var(--tau-dur-short) var(--tau-ease-standard);
}
.xform.is-open .dim-card-body { opacity: 1; transform: none; transition-delay: 140ms; }

.xform-close {
  position: absolute;
  top: var(--tau-s4); right: var(--tau-s4);
  opacity: 0;
  transition: opacity var(--tau-dur-short) var(--tau-ease-standard);
}
.xform.is-open .xform-close { opacity: 1; transition-delay: 140ms; }

.dim-card-moments { margin: 0; max-width: 78ch; font-size: var(--tau-text-md); line-height: var(--tau-leading-body); color: var(--tau-ink-soft); }
.dim-card-moments q { font-family: var(--tau-font-serif); color: var(--tau-ink); }
.dim-card-body .aside { max-width: 78ch; }

/* Setting a passage apart WITHOUT a left bar — a hairline above and a label
   naming what the passage is. The bar said "quoted or subordinate" without
   saying which, and stacked with the panel's own edges it put three vertical
   lines in one card. */
.aside { display: flex; flex-direction: column; gap: var(--tau-s2); }
.aside::before { content: ""; display: block; height: 1px; background: color-mix(in oklab, var(--level-fg, var(--tau-line-strong)) 30%, transparent); }
.aside .eyebrow { color: var(--level-fg, var(--tau-ink-soft)); }
.aside p { font-size: var(--tau-text-md); color: var(--tau-ink-soft); margin: 0; }

/* Source: app/web/report.css, the .turnplot block  (2026-08-19)
 *
 * The session as a map: one square per student turn, at one of four heights.
 * NOTHING is drawn between two turns — the two charts this replaced both ran a
 * moving average over ordinal codes, which is arithmetic on labels. Never add
 * a trend line here, however much it looks like it wants one.
 *
 * The plot is a fixed 952px at this session's 44 turns and the page caps at
 * 1080px, so it fits without scrolling on the desktop layout; .turnplot-wrap
 * keeps the horizontal scroll for everything narrower. The app pairs it with a
 * 300px detail card at 1180px and up — below that the card drops underneath
 * rather than squeezing the plot, because a narrower plot means narrower
 * squares and the square is the thing being pointed at. This page is 1080px,
 * so the stacked arrangement is the one that applies.
 */

.turnplot-ag-4 { fill: var(--tau-agency-4); }
.turnplot-ag-3 { fill: var(--tau-agency-3); }
.turnplot-ag-2 { fill: var(--tau-agency-2); }
.turnplot-ag-1 { fill: var(--tau-agency-1); }

/* Plot and detail card side by side, as the component specifies. The arithmetic
   is fixed and worth writing down: 952 (plot) + 20 (--tau-s5 gap) + 300 (card)
   = 1272 of content, plus the card's 2x32 padding and 2x1 border = a 1338px
   card, plus the page's 2x20 gutter = 1378px of viewport before something has
   to give. (The border is easy to forget and costs exactly the 2px that clips
   the last turn off the plot.)
   The page column is 1080px, so the figure breaks out of it rather than the
   layout being changed to fit — squeezing the plot instead would shrink the
   turn squares, and the square is the thing being pointed at.
   Under 1378px it stacks, which is the component's own fallback. */
.turnplot-breakout {
  width: min(1338px, calc(100vw - 2 * var(--tau-s5)));
  margin-inline: calc(50% - min(1338px, 100vw - 2 * var(--tau-s5)) / 2);
}
.turnplot-row { align-self: stretch; width: 100%; display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--tau-s5); align-items: start; }
@media (min-width: 1378px) {
  .turnplot-row { grid-template-columns: minmax(0, 1fr) 300px; }
}

.turnplot-wrap { overflow-x: auto; min-width: 0; padding-bottom: var(--tau-s1); }
.turnplot { display: block; }
.turnplot text { font-family: var(--tau-font-ui); }
.turnplot .tp-row     { font-size: var(--tau-text-xs); fill: var(--tau-ink-soft); text-anchor: end; }
.turnplot .tp-grid    { stroke: var(--tau-line); stroke-width: 1; }
.turnplot .tp-axis    { stroke: var(--tau-line-strong); stroke-width: 1; }
.turnplot .tp-tick    { font-size: var(--tau-text-xs); fill: var(--tau-ink-faint); text-anchor: middle; font-family: var(--tau-font-mono); }
.turnplot .tp-caption { font-size: var(--tau-text-xs); fill: var(--tau-ink-faint); }

.turnplot .tp-run-name { font-size: var(--tau-text-xs); font-weight: 600; }

/* Selection, frozen. In the product these are three states — hover is a thin
   ink outline, selection a thick forest ring PLUS everything else stepping
   back — and the step-back is what keeps one square among forty-four findable.
   Here only the selected state exists: nothing is clickable, so no cursor, no
   hover rule and no focus ring, which would all promise an interaction the
   page cannot honour. */
.turnplot .tp-turn rect { stroke: transparent; stroke-width: 2; }
.turnplot.is-selecting .tp-turn:not(.is-open) rect { opacity: 0.4; }
.turnplot .tp-turn.is-open rect { stroke: var(--tau-forest); stroke-width: 3; }

/* The card the whole figure sits in, and the finding above it. The finding
   leads and the mark is subordinate — designsystem.md, "On a single subject". */
.turnplot-card { gap: var(--tau-s5); padding: var(--tau-s6) var(--tau-s7); }
.turnplot-title { margin: 0; font-size: var(--tau-text-xl); font-weight: 700; letter-spacing: -0.01em; }
.turnplot-finding { margin: 0; font-size: var(--tau-text-md); line-height: var(--tau-leading-body); color: var(--tau-ink-soft); max-width: 86ch; }
.turnplot-finding strong { color: var(--tau-ink); }

.turnplot-legend { align-self: stretch; display: flex; flex-wrap: wrap; gap: var(--tau-s3) var(--tau-s7); }
.turnplot-legend-group { display: flex; flex-direction: column; gap: var(--tau-s2); }
.turnplot-legend-group > b { font-family: var(--tau-font-mono); font-size: var(--tau-text-xs); font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase; color: var(--tau-ink-faint); }
.turnplot-legend-items { display: flex; flex-wrap: wrap; gap: var(--tau-s4); }
.turnplot-legend-item { display: flex; align-items: center; gap: var(--tau-s2); font-size: var(--tau-text-sm); color: var(--tau-ink-soft); }
.swatch-run-high { background: var(--tau-band-4-bg); }
.swatch-run-low  { background: var(--tau-band-2-bg); }

/* The detail card is permanent furniture, not a tooltip: a turn's own words
   are the part a student can act on, and the charts this replaced hid them
   behind hover, unreachable on the Chromebooks and tablets this ships to. */
/* min-height matches the app: the card is permanent furniture in the product,
   sized so it does not resize as turns are selected. The max-width applies only
   in the stacked fallback, where a one-quote card stretched to full width would
   read as a banner rather than as the note beside a chart. */
.turnplot-detail { border: 1px solid var(--tau-line); border-radius: var(--tau-r-lg); background: var(--tau-surface-2); padding: var(--tau-s5); min-height: 15rem; display: flex; flex-direction: column; gap: var(--tau-s4); max-width: 46rem; }
@media (min-width: 1378px) { .turnplot-detail { max-width: none; } }
.turnplot-detail-which { font-family: var(--tau-font-mono); font-size: var(--tau-text-xs); letter-spacing: 0.08em; text-transform: uppercase; color: var(--tau-ink-faint); }
.turnplot-detail-words { margin: 0; display: flex; flex-direction: column; gap: var(--tau-s3); }
.turnplot-detail-words q { font-family: var(--tau-font-serif); font-size: var(--tau-text-lg); line-height: 1.55; color: var(--tau-ink); }
.turnplot-detail-reading { margin-top: auto; border-top: 1px solid var(--tau-line); padding-top: var(--tau-s4); display: flex; flex-direction: column; gap: var(--tau-s2); }
.turnplot-detail-band { display: flex; align-items: center; gap: var(--tau-s2); font-size: var(--tau-text-md); font-weight: 600; }
.turnplot-detail-band .turnplot-swatch { width: 12px; height: 12px; }
.turnplot-detail-note { margin: 0; font-size: var(--tau-text-sm); color: var(--tau-ink-soft); }

/* .turnplot-swatch is a legend dot in the app, coloured by an inline style.
   Here it reuses the .turnplot-ag-* classes, which set `fill` for SVG — so it
   needs `background` from the same token to show up as an HTML span. */
.turnplot-swatch { width: 14px; height: 14px; border-radius: 3px; flex: none; }
.turnplot-swatch.turnplot-ag-4 { background: var(--tau-agency-4); }
.turnplot-swatch.turnplot-ag-3 { background: var(--tau-agency-3); }
.turnplot-swatch.turnplot-ag-2 { background: var(--tau-agency-2); }
.turnplot-swatch.turnplot-ag-1 { background: var(--tau-agency-1); }

/* The prose that introduces a full-width figure, rather than sitting beside
   it in a split. Matches .split-text's measure so the two read as one page. */
.split-lead { max-width: 62ch; margin-bottom: var(--tau-s7); }
.split-lead h3 { margin: 0; font-size: clamp(21px, 2.4vw, 27px); line-height: var(--tau-leading-tight); letter-spacing: -0.02em; font-weight: 600; }

/* ---------- split rows ---------- */

/* A screenshot beside the paragraph that explains it, rather than a
   full-width image the reader has to hold in memory while they scroll to the
   text. The figure column is the wider of the two: these are dense product
   screens, and the prose beside them is short by design. */
.split {
  display: grid;
  grid-template-columns: 1.15fr 1fr;
  gap: clamp(var(--tau-s6), 4vw, var(--tau-s8));
  align-items: center;
}
.split + .split { margin-top: clamp(48px, 7vw, 88px); }

/* Two thirds to the figure, one to the text. The four dimension cards are a
   2x2 grid that has to hold a serif question and a meter per cell; at the
   default 1.15fr the questions wrapped to three lines and the cards read as
   cramped. Layout only — a page sheet's one allowed move on a component. */
.split-wide { grid-template-columns: 2fr 1fr; }

/* Alternating sides keeps a run of three rows from reading as a list. Order
   is swapped visually only — source order stays figure-then-text so the
   caption follows its image for a screen reader. */
.split-flip .split-media { order: 2; }

.split-text h3 {
  margin: 0;
  font-size: clamp(21px, 2.4vw, 27px);
  line-height: var(--tau-leading-tight);
  letter-spacing: -0.02em;
  font-weight: 600;
}
.split-text p {
  margin: var(--tau-s4) 0 0;
  max-width: 46ch;
  color: var(--tau-ink-soft);
}
.split-text .eyebrow { margin-bottom: var(--tau-s3); }

@media (max-width: 860px) {
  .split { grid-template-columns: 1fr; gap: var(--tau-s6); }
  .split-flip .split-media { order: 0; }
}

/* ---------- section rhythm ---------- */

.section { padding: clamp(52px, 8vw, 96px) 0; }
.section + .section { border-top: 1px solid var(--tau-line); }

/* Type and colour come from components.css. A page sheet may set layout on a
   component and nothing else, so this adds spacing only. */
.eyebrow { margin: 0 0 var(--tau-s3); }

/* --- .leaf-head — the leaf beside a chapter's own heading.

   On the four headings that carry the page's argument, not on every h2: the
   dimension and evidence figures have their own eyebrows, and marking those
   too would make the leaf punctuation rather than emphasis.

   Sized in em and aligned off the heading's own box, so it tracks the h2's
   clamp() from 28px to 44px without a second breakpoint. It is ::before
   content rather than an <img>, so it stays out of the accessibility tree —
   the heading already says what the section is, and a second announcement of
   a decorative leaf is noise to a screen reader.

   Still a mask: assets/leaf-*.svg carry baked-in fills, and theme here is a
   data-theme attribute rather than prefers-color-scheme, so an <img> would
   need a second file swapped by attribute selector and would still miss any
   later token change. Masked, the mark takes --tau-forest and follows the ramp
   for free. Any of the three leaf files works as the shape source; sage is the
   one used because its path is the shared silhouette. */
.leaf-head::before {
  content: "";
  display: inline-block;
  /* The path fills only ~71% of its square viewBox, so the box runs larger
     than cap height to land the visible glyph at it. */
  width: 1.02em;
  height: 1.02em;
  margin-right: 0.26em;
  /* The glyph is optically centred in a square viewBox, so baseline alignment
     would hang it low against cap height. */
  vertical-align: -0.06em;
  background-color: var(--leaf-head-ink, var(--tau-forest));
  -webkit-mask: url(assets/leaf-sage.svg) center / contain no-repeat;
          mask: url(assets/leaf-sage.svg) center / contain no-repeat;
}
/* On a forest ground the default ink is the ground. */
.band-forest .leaf-head::before { --leaf-head-ink: var(--tau-on-forest); }

/* .band-statement sets letter-spacing: -0.03em, which tightens the words but
   not the em-based gap, so the same margin reads loose against it. */
.band-statement.leaf-head::before { margin-right: 0.18em; }

.section h2 {
  margin: 0;
  font-size: clamp(28px, 4.2vw, 44px);
  line-height: 1.08;
  letter-spacing: -0.025em;
  font-weight: 700;
  max-width: 20ch;
}

.section p { max-width: var(--m-measure); color: var(--tau-ink-soft); }
.section h2 + p { margin-top: var(--tau-s5); font-size: var(--m-text-lead); }
.section p strong { color: var(--tau-ink); font-weight: 600; }

/* ---------- the three-step strip ---------- */

/* NOT .steps — that name belongs to the product's five-pip score meter in
   components.css, and squatting on it turned every meter on this page into a
   3-column grid. Marketing never redefines a component's name. */

/* Numbered because the sequence is real — the order is the workflow, not
   decoration on an unordered list. */
.workflow {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--tau-s5);
  margin: var(--tau-s8) 0 0;
  padding: 0;
  list-style: none;
  counter-reset: step;
}
.workflow li {
  counter-increment: step;
  padding-top: var(--tau-s5);
  border-top: 2px solid var(--tau-forest);
}
.workflow li::before {
  content: counter(step);
  display: block;
  font-size: var(--tau-text-sm);
  font-weight: 700;
  letter-spacing: var(--tau-track-label);
  color: var(--tau-forest);
  margin-bottom: var(--tau-s2);
}
.step-name {
  margin: 0;
  font-size: var(--tau-text-xl);
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--tau-ink);
  max-width: none;
}
.step-desc {
  margin: var(--tau-s2) 0 0;
  font-size: var(--tau-text-md);
  color: var(--tau-ink-soft);
  max-width: none;
}
@media (max-width: 760px) {
  .workflow { grid-template-columns: 1fr; gap: var(--tau-s6); }
}

/* ---------- dimension definitions ---------- */

/* NOT .dim*/.*/ — the app owns .def-name (the 172px trend-row label) and
   .dim-grid/.dim-quad (the scored quadrants). This is the marketing
   definition card that names what each dimension asks, so it gets its own
   namespace. The previous name collided: the transcribed .def-name { width:
   172px } was being applied to these cards too, pushing the grid past the
   viewport at 390px and 768px. */

.defs {
  display: grid;
  gap: var(--tau-s4);
  grid-template-columns: repeat(4, 1fr);
  margin: var(--tau-s7) 0 0;
}
@media (max-width: 760px) { .defs { grid-template-columns: repeat(2, 1fr); } }
.def {
  padding: var(--tau-s5);
  background: var(--tau-surface);
  border: 1px solid var(--tau-line);
  border-radius: var(--tau-r-md);
}
.def-name {
  margin: 0;
  font-size: var(--tau-text-md);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--tau-ink);
  max-width: none;
}
.def-desc {
  margin: var(--tau-s2) 0 0;
  font-size: var(--tau-text-sm);
  color: var(--tau-ink-soft);
  max-width: none;
}

/* ---------- forest band ---------- */

/* The page's one anchor. See the file header — this is a documented marketing
   extension of forest's six jobs, used exactly once, carrying a claim only. */
.band-forest {
  background: var(--tau-forest);
  color: var(--tau-on-forest);
  padding: clamp(56px, 9vw, 104px) 0;
}
.band-forest .eyebrow { color: var(--tau-on-forest); opacity: 0.7; }
.band-forest p { color: var(--tau-on-forest); }

/* The opening band is prose under a heading, not a one-line claim, so it uses
   .section's h2 + measure rather than .band-statement. Full-strength white for
   two paragraphs of body copy glares; .band-after already settled that at 0.82
   for the closing band, so the same figure applies here. The heading stays at
   full strength — it is the thing the band exists to carry. */
.band-forest.section p { opacity: 0.82; }
.band-forest.section h2 { opacity: 1; }
.band-statement {
  margin: 0;
  max-width: 22ch;
  font-size: clamp(28px, 4.6vw, 50px);
  line-height: 1.1;
  letter-spacing: -0.03em;
  font-weight: 700;
}
.band-forest .band-after {
  margin: var(--tau-s6) 0 0;
  max-width: 48ch;
  font-size: var(--m-text-lead);
  opacity: 0.82;
}

/* ---------- questions ---------- */

/* Two columns once there's room. Six questions in one stack is a long scroll
   for content most readers sample rather than read through. */
.qa {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  column-gap: clamp(var(--tau-s6), 5vw, var(--tau-s9));
  row-gap: var(--tau-s7);
  margin-top: var(--tau-s7);
}
.qa p { max-width: 46ch; }
@media (max-width: 820px) {
  .qa { grid-template-columns: 1fr; }
}
.qa-item h3 {
  margin: 0;
  font-size: var(--tau-text-xl);
  letter-spacing: -0.015em;
  font-weight: 600;
}
.qa-item p { margin: var(--tau-s2) 0 0; }

/* ---------- closing CTA ---------- */

.close {
  padding: clamp(56px, 9vw, 104px) 0;
  border-top: 1px solid var(--tau-line);
  background: var(--tau-surface-2);
}
/* Centred — the one place on the page that is. A terminal beat reads as an
   ending when it stops using the left rail everything else is hung on. */
.close { text-align: center; }
.close h2 {
  margin: 0 auto;
  font-size: clamp(30px, 5vw, 52px);
  line-height: 1.05;
  letter-spacing: -0.03em;
  font-weight: 700;
  max-width: 18ch;
}
.close p {
  max-width: 52ch;
  margin: var(--tau-s5) auto 0;
  font-size: var(--m-text-lead);
  color: var(--tau-ink-soft);
}
.close .hero-actions { margin-top: var(--tau-s6); justify-content: center; }

/* ---------- footer ---------- */

.foot {
  padding: var(--tau-s7) 0;
  border-top: 1px solid var(--tau-line);
  font-size: var(--tau-text-sm);
  color: var(--tau-ink-faint);
}
.foot-inner {
  display: flex;
  flex-wrap: wrap;
  gap: var(--tau-s5);
  align-items: center;
  justify-content: space-between;
}
.foot a { color: var(--tau-ink-soft); text-decoration: none; }
.foot a:hover { color: var(--tau-forest); }

/* ---------- scroll reveal ---------- */

/* Scoped to .js — set by the head script. With JS off nothing would ever add
   .in, so an unscoped rule would hide the whole page permanently.
   transform/opacity only, so a reveal never triggers layout. */
.js .reveal {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity var(--tau-dur-medium) var(--tau-ease-decelerate),
              transform var(--tau-dur-medium) var(--tau-ease-decelerate);
}
.js .reveal.in { opacity: 1; transform: none; }
