/* ============================================================================
   DRAGONEYE — holding / early-access page
   Consumes the shared design tokens (tokens.css). Dark = gold on charcoal;
   light = deep bronze on warm bone. Never plain white on light.
   Gold hairline = a tool. White hairline = data. (Same language as the app.)
   ========================================================================== */

@font-face {
  font-family: "Enclave";
  src: url("assets/EnclaveRegular.ttf") format("truetype");
  font-display: swap;
}

*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  line-height: var(--leading-body);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
img { max-width: 100%; display: block; }

/* ── THE STAGE — the live quarry, pinned behind the page ─────────────────── */
.stage {
  position: fixed; inset: 0; z-index: 0;
  overflow: hidden;
  background:
    radial-gradient(ellipse at 50% 40%, #14181a 0%, var(--bg) 62%);
  transition: opacity var(--dur-slow) var(--ease-out), filter var(--dur-slow) var(--ease-out);
}
:root[data-theme="light"] .stage {
  background: radial-gradient(ellipse at 50% 40%, #ffffff 0%, var(--bg) 60%);
}
/* Pointer events OFF until we hand the controls over — otherwise the canvas
   would eat the scroll and the visitor would be trapped in the viewer. */
/* Fade the site in once tiles are actually on screen — never flash an empty void. */
.stage .scene {
  position: absolute; inset: 0; pointer-events: none;
  opacity: 0; transition: opacity 1.2s var(--ease-out);
}
.stage .scene.scene-ready { opacity: 1; }

/* The frame offset is driven per-frame by the rAF loop (story.js), on the SAME spline
   as the camera. It must NOT have a CSS transition — a transition here would be a second
   easing curve fighting the first, which is precisely what made the motion feel wrong. */
/* Cesium sizes its own canvas from the container and does NOT track a resized
   window on its own. Without this the canvas keeps its birth width — on an
   ultrawide the model ends up marooned a quarter of the way across the screen
   instead of centred, which reads as "lost" rather than "hero". */
.stage .scene,
.stage .cesium-widget,
.stage .cesium-widget canvas {
  width: 100% !important;
  height: 100% !important;
  display: block;
}
body.controls-live .stage .scene { pointer-events: auto; }
body.controls-live { cursor: grab; }
body.controls-live:active { cursor: grabbing; }

.stage-veil {
  position: absolute; inset: 0; pointer-events: none;
  /* Sit the copy on something legible without hiding the model. */
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--bg) 72%, transparent) 0%, transparent 34%),
    linear-gradient(0deg,  color-mix(in srgb, var(--bg) 82%, transparent) 0%, transparent 30%);
}
.breath {
  position: absolute; left: 50%; top: -18vh; width: 130vw; height: 110vh;
  transform: translateX(-50%); pointer-events: none;
  background: radial-gradient(ellipse at 50% 30%, var(--bg-wash) 0%, transparent 62%);
}
/* Once the story is done, dim the stage so the content below reads cleanly. */
.stage.stage-dim { opacity: .18; filter: saturate(.5); }

/* No WebGL / phone / reduced-motion / tileset failed → a static stage. The page
   must be excellent without the 3D, not dependent on it. */
.stage.scene-off .scene, .stage .scene.scene-failed { display: none; }
.stage.scene-off {
  background:
    radial-gradient(ellipse at 50% 30%, var(--bg-wash) 0%, transparent 60%),
    var(--bg);
}
.stage.scene-off::after {
  content: ""; position: absolute; inset: 0;
  background-image:
    linear-gradient(var(--grid-line) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
  background-size: 64px 64px;
  mask-image: radial-gradient(ellipse at 50% 40%, #000 20%, transparent 75%);
}

/* ── THE STORY — beats that float over the stage ─────────────────────────── */
/* THE BUG THAT KILLED THE DRAG: these beats are full-viewport boxes sitting on top
   of the canvas, so they swallowed every mouse event before Cesium ever saw it —
   the site looked interactive and did nothing. The overlay is now transparent to
   the pointer, and only the things you can actually click take events back. */
.story { position: relative; z-index: 1; pointer-events: none; }
.beat-in, .beat-card, .story .btn, .story a, .scroll-cue { pointer-events: auto; }
.beat {
  min-height: 100vh;
  display: flex; align-items: center;
  /* Constrained, and CENTRED. Left/right beats used to pin themselves to the
     viewport edges, so on a wide monitor the copy fled into the corners and left
     a vacuum down the middle with the model marooned in it. The column now holds
     its width and the card sits just off-centre — the model keeps the rest. */
  max-width: 1320px;
  margin: 0 auto;
  padding: var(--space-10) var(--space-6);
}
/* Hero: copy LEFT, quarry RIGHT. Centred text over a centred model is a fight
   neither wins — the headline cut straight through the pit and the sub-copy washed
   out over the benches. Asymmetry is also simply more alive: it's the difference
   between a certificate and a poster. */
.beat--hero {
  justify-content: flex-start;
  align-items: center;
  text-align: left;
  position: relative;
}
.beat--hero .beat-in {
  max-width: 64ch;
  /* Lifted by what was 0.006 of scroll (86 px at 1080): Jeremy, 2026-09-15, the text sat
     too low at the very top of the page. `top`, not transform: story.js writes the
     transform every frame for the fade. Relative, so the page length does not change. */
  position: relative; top: -8vh;
}
/* ⭐ THE HERO'S SCRIM, Inspect's answer, attached to the COPY rather than the page.
   Jeremy, 2026-09-15: a left-edge band darkened empty ground at the screen edge and ran
   out exactly where the grey paragraph sits over bright rock. A soft oval behind the
   words themselves, sized so it never resolves into a shape. On the copy block, so it
   moves and fades with the words (story.js fades .beat-in); nothing to keep in step.
   The transform story.js writes on .beat-in makes it a stacking context, so z-index -1
   stays behind the text and in front of the model. */
body:not(.page-inspect) .beat--hero .beat-in::before {   /* Inspect has its own, on the section */
  content: "";
  position: absolute; inset: -16vh -12vw;
  pointer-events: none; z-index: -1;
  background: radial-gradient(ellipse 58% 62% at 44% 50%,
    color-mix(in srgb, var(--bg) 90%, transparent) 0%,
    color-mix(in srgb, var(--bg) 64%, transparent) 48%,
    transparent 78%);
}
/* The sub-copy is the part that washes out: muted grey over lit rock. Not on the title,
   which is a clipped gradient (a shadow under background-clip:text smears). */
.beat--hero .hero-sub {
  text-shadow: 0 1px 16px color-mix(in srgb, var(--bg) 80%, transparent);
}
.beat--hero .hero-cta { justify-content: flex-start; }
.beat--left  { justify-content: flex-start; }
.beat--right { justify-content: flex-end; }
/* A wordless beat. The reverse elevation has to land on its own — a caption there
   would be a narrator talking over the best shot in the film. */
.beat--quiet { min-height: 100vh; }
/* Scroll room for a sequence to play out in: no words, just time. ⛔ Its height is in the
   camera film (the last hold in SURVEY_SHOTS, scene.js); change one, re-derive the other. */
.beat--room { min-height: 200vh; }
/* The hand-over beat is DELIBERATELY TALL and its panel is sticky. That extra
   height is the room to actually play with the site — without it, the moment you
   nudge the wheel you're gone, which is no invitation at all. */
.beat--take {
  justify-content: center;
  text-align: center;
  align-items: flex-start;
  min-height: 210vh;
  position: relative;
}
.beat--take .beat-in { position: sticky; top: 18vh; transition: opacity var(--dur-base) var(--ease-out); }
/* THE BUG THAT MADE "PLAY WITH IT" DEAD: on engage the invite panel was only faded to
   opacity 0 — still in the layout, still hit-testable, still sitting across the middle
   of the screen. Every drag and every wheel landed on an invisible button instead of the
   quarry. Invisible is not the same as out of the way. */
.beat--take .beat-in.is-gone {
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
}

.take-note {
  margin: var(--space-3) 0 0;
  color: var(--text-faint);
  font-family: var(--font-mono); font-size: var(--text-xs);
  letter-spacing: .06em;
}

/* ── EXPLORING — only exists once they've pressed "Take control" ─────────── */
/* While engaged the canvas owns the wheel (zoom, like the viewer), so the page
   must NOT scroll underneath — and the way out must be impossible to miss. */
body.exploring { overflow: hidden; }
body.exploring .stage .scene { pointer-events: auto; cursor: grab; }
body.exploring .stage .scene:active { cursor: grabbing; }
body.exploring .nav { opacity: .25; transition: opacity var(--dur-base); }
body.exploring .nav:hover { opacity: 1; }

.explore {
  position: fixed; inset: 0; z-index: 5;
  pointer-events: none;                 /* the canvas beneath must stay grabbable */
  padding: var(--space-6);
  display: flex; align-items: flex-start; justify-content: space-between;
}
.explore[hidden] { display: none; }
.explore-rail, .readout, .explore-hud { pointer-events: auto; }

/* The rail — one button per real measurement. Left side, out of the way. */
.explore-rail {
  display: flex; flex-direction: column; gap: .5rem;
  width: 260px;
  padding: var(--space-5);
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  backdrop-filter: blur(20px) saturate(1.1);
  border: 1px solid var(--hairline-tool);
  box-shadow: var(--shadow-lg);
  margin-top: 4rem;
}
.rail-title {
  margin: 0 0 var(--space-3);
  color: var(--accent);
  font-family: var(--font-mono); font-size: var(--text-xs);
  letter-spacing: var(--tracking-caps); text-transform: uppercase;
}
.rail-title span { display: block; color: var(--text-faint); letter-spacing: .08em; }
.rail-btn {
  display: flex; flex-direction: column; align-items: flex-start; gap: .1rem;
  padding: .6rem .8rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-subtle);
  background: transparent;
  color: var(--text);
  text-align: left;
  cursor: pointer;
  transition: border-color var(--dur-fast), background var(--dur-fast);
}
.rail-btn:hover { border-color: var(--accent-line); background: var(--accent-wash); }
.rail-btn[aria-pressed="true"] {
  border-color: transparent;
  background: var(--accent-cta);
  color: var(--on-accent);
}
.rail-name { font-weight: 600; font-size: var(--text-sm); }
.rail-sub {
  font-family: var(--font-mono); font-size: 11px;
  color: var(--text-faint); letter-spacing: .04em;
}
.rail-btn[aria-pressed="true"] .rail-sub { color: color-mix(in srgb, var(--on-accent) 70%, transparent); }
.rail-clear {
  margin-top: .3rem;
  padding: .4rem;
  background: transparent; border: 0;
  color: var(--text-faint);
  font-family: var(--font-mono); font-size: var(--text-xs);
  letter-spacing: .08em; cursor: pointer;
}
.rail-clear:hover { color: var(--text); }

/* The readout — the tease. One set of figures, never the panel. */
.readout {
  position: absolute; left: var(--space-6); bottom: var(--space-6);
  width: 300px;
  padding: var(--space-5);
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--bg) 76%, transparent);
  backdrop-filter: blur(20px);
  border: 1px solid var(--hairline-tool);
  box-shadow: var(--shadow-lg);
}
.readout[hidden] { display: none; }
.readout-name {
  margin: 0 0 var(--space-3);
  font-family: var(--font-display); font-weight: 700; font-size: var(--text-lg);
}
.readout-figs { margin: 0 0 var(--space-3); }
.readout-figs > div {
  display: flex; justify-content: space-between; gap: var(--space-4);
  padding: .35rem 0;
  border-bottom: 1px solid var(--divider);
}
.readout-figs > div:last-child { border-bottom: 0; }
.readout-figs dt { color: var(--text-muted); font-size: var(--text-sm); }
.readout-figs dd {
  margin: 0; font-family: var(--font-mono); font-weight: 600;
  color: var(--accent);
}
:root[data-theme="light"] .readout-figs dd { color: var(--bronze-strong); }
.readout-foot { margin: 0; color: var(--text-faint); font-size: 11px; }

/* Volume Comparison controls — the app's real toggles/slider, in the readout. */
.vc-controls { margin: 0 0 var(--space-3); padding-top: var(--space-3); border-top: 1px solid var(--divider); }
.vc-controls[hidden] { display: none; }
.vc-label {
  margin: 0 0 .5rem; color: var(--text-faint);
  font-family: var(--font-mono); font-size: var(--text-xs);
  letter-spacing: .06em; text-transform: uppercase;
}
.vc-toggles { display: grid; grid-template-columns: 1fr 1fr; gap: .4rem; margin-bottom: .6rem; }
.vc-toggle {
  padding: .4rem .5rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-subtle);
  background: transparent; color: var(--text-muted);
  font-size: var(--text-xs); cursor: pointer;
  text-align: center;
  transition: border-color .15s ease, background .15s ease, color .15s ease;
}
.vc-toggle:hover { border-color: var(--accent-line); color: var(--text); }
.vc-toggle[aria-pressed="true"] { border-color: transparent; background: var(--accent-cta); color: var(--on-accent); }
.vc-slider { display: flex; align-items: center; justify-content: space-between; gap: .6rem; }
.vc-slider span { color: var(--text-muted); font-size: var(--text-xs); white-space: nowrap; }
.vc-slider input[type="range"] { flex: 1; accent-color: var(--accent); cursor: pointer; }
/* Grid is a mode toggle — lock out the layer/stilts controls while it's on. */
.vc-controls.grid-on .vc-toggle:not(.vc-grid),
.vc-controls.grid-on .vc-slider { opacity: .35; pointer-events: none; }

.explore-hud {
  display: flex; flex-direction: column; align-items: flex-end; gap: .6rem;
  margin-top: 4rem;
}

/* Tool chips — real saved measurements from this survey. Gold = a tool. */
.tools {
  display: flex; flex-wrap: wrap; justify-content: center; gap: .5rem;
  margin: var(--space-6) 0;
}
.tool-chip {
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .5rem .9rem;
  border-radius: var(--radius-pill);
  border: 1px solid var(--hairline-tool);
  background: transparent;
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: .06em;
  cursor: pointer;
  transition: color var(--dur-fast), background var(--dur-fast), border-color var(--dur-fast);
}
.tool-chip:hover { color: var(--text); background: var(--accent-wash); }
.tool-chip[aria-pressed="true"] {
  color: var(--on-accent);
  background: var(--accent-cta);
  border-color: transparent;
}
.chip-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent-deep);
}
.tool-chip[aria-pressed="true"] .chip-dot { background: var(--on-accent); }

/* HUD — pinned bottom-right while the visitor has the controls. */
.hud {
  position: sticky;
  top: 78vh;
  margin-left: auto;
  display: none;
  flex-direction: column; align-items: flex-end; gap: .4rem;
  pointer-events: auto;
}
body.controls-live .hud { display: flex; }
.hud-btn {
  width: 40px; height: 40px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--hairline-tool);
  background: color-mix(in srgb, var(--bg) 70%, transparent);
  backdrop-filter: blur(12px);
  color: var(--accent);
  font-size: var(--text-lg);
  cursor: pointer;
}
.hud-btn:hover { background: var(--accent-wash); }
.hud-reset {
  width: auto; height: auto; padding: .45rem .8rem;
  font-family: var(--font-mono); font-size: var(--text-xs);
  letter-spacing: .08em;
}
/* ── THE CONTROL CARD — how to drive it, drawn rather than described ──────────
   ⭐ A MINI MOUSE PER ROW, NOT ONE BIG DIAGRAM WITH LEADER LINES. The big-diagram
   version needs the eye to travel from a label, along a line, to a part, and back.
   Six little mice each lighting their own button is read in one pass, and it stays
   legible at the size a HUD can afford. Gold = the part this row is about, which is
   the same language the rest of the page uses: gold is the instrument.
   ⛔ Every row must match scene.js setEngaged and Cesium's bindings. If the two
   drift, the card teaches a control the product does not have — which is the exact
   fault it was built to fix. */
.mouse-card {
  padding: .85rem 1rem .9rem;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--bg) 84%, transparent);
  backdrop-filter: blur(16px) saturate(1.1);
  border: 1px solid color-mix(in srgb, var(--accent-line) 42%, transparent);
  box-shadow: var(--shadow-md);
}
.mouse-title {
  display: flex; align-items: baseline; gap: .5rem;
  margin: 0 0 .6rem;
  font-family: var(--font-mono); font-size: var(--text-xs);
  letter-spacing: var(--tracking-caps); text-transform: uppercase;
  color: var(--accent);
}
.mouse-title span {
  text-transform: none; letter-spacing: .04em;
  color: var(--text-faint);
}
.mouse-keys { margin: 0; padding: 0; list-style: none; display: grid; gap: .42rem; }
.mouse-keys li {
  display: grid; grid-template-columns: 22px auto 1fr; align-items: center;
  gap: .55rem;
  font-size: var(--text-xs);
}
.mouse-keys b {
  font-family: var(--font-mono); font-weight: 500;
  color: var(--text); letter-spacing: .03em; white-space: nowrap;
}
.mouse-keys span { color: var(--text-muted); }

/* The mouse itself: one shape, three parts, CSS decides which is lit. */
.mk { width: 22px; height: 32px; display: block; overflow: visible; }
.mk .mk-body { fill: none; stroke: var(--hairline-data); stroke-width: 1.4; }
.mk .mk-div  { stroke: var(--hairline-data); stroke-width: 1.1; }
.mk .mk-l, .mk .mk-r { fill: none; }
.mk .mk-w { fill: none; stroke: var(--hairline-data); stroke-width: 1.1; }
.mk--left  .mk-l { fill: color-mix(in srgb, var(--accent) 80%, transparent); }
.mk--right .mk-r { fill: color-mix(in srgb, var(--accent) 80%, transparent); }
.mk--wheel .mk-w { fill: color-mix(in srgb, var(--accent) 80%, transparent);
                   stroke: var(--accent); }

@media (max-width: 1100px) { .mouse-card { display: none; } }
.beat-in { max-width: 64ch; }

/* A beat card is a whisper, not a box — no hard border, just enough veil to read.
   It deliberately OVERLAPS the model rather than sitting beside it; that overlap
   is what makes the two feel like one composition instead of two columns. */
.beat-card {
  will-change: opacity, transform;
  /* ⛔ 46ch, NOT 42. The headlines carry deliberate phrase breaks (<br>), and at
     42ch with a 38px h2 five of the thirteen across Survey and Inspect overflowed
     their own phrasing: "Everyone who needs the site / is already standing on it."
     came out as four ragged lines with "on it." orphaned. Measured at 1080: every
     headline on both pages holds two lines at 46ch + 32px. Widening alone would
     also have worked at 54ch, but that pushes the body copy past a comfortable
     measure, so the fix is split between the two. */
  max-width: 46ch;
  padding: var(--space-6) var(--space-6) var(--space-6) var(--space-5);
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--bg) 58%, transparent);
  backdrop-filter: blur(20px) saturate(1.15);
  border: 1px solid color-mix(in srgb, var(--accent-line) 40%, transparent);
  box-shadow: var(--shadow-lg);
}
.beat-card h2 {
  margin: 0 0 var(--space-3);
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(1.45rem, 2.1vw, 2rem);
  line-height: var(--leading-snug); letter-spacing: -0.015em;
}
.beat-card p { margin: 0; color: var(--text-muted); font-size: var(--text-base); }
/* ⛔ AND THIS IS WHY EVERY KICKER ON THE SITE WAS GREY. .kicker sets the gold, but
   a kicker is a <p> inside a card, and `.beat-card p` (0,1,1) beats `.kicker`
   (0,1,0) on specificity, so the muted body colour won every time — on Survey and
   Inspect alike. The rule below is the one that was missing, not a new colour.
   Jeremy spotted it on b-site, 2026-09-16. */
.beat-card .kicker { color: var(--accent); }
:root[data-theme="light"] .beat-card .kicker { color: var(--bronze-strong); }
/* ⛔ THERE WAS NO PARAGRAPH SPACING ON THIS PAGE AT ALL. Every multi-paragraph beat
   card ran its paragraphs together into one block, which reads as a wall and loses
   the break the writing was built around. Costs no page height: .beat is min-height
   100vh and the tallest card is well under that, so the sections do not grow and no
   camera mark moves. Verified by the shot table after the change. */
.beat-card p + p { margin-top: var(--space-4); }

/* ── LEGIBILITY OVER A LIT SUBJECT ───────────────────────────────────────────
   The quarry is dark grey rock, and 58% of ground is plenty to read over it. Worting
   House is red brick and white sash in daylight, it fills far more of the frame at
   every shot, and it passes UNDER the copy at every zoom. Same veil, and the words
   wash out. This is not a taste change, it is the difference between copy that can be
   read at all shots and copy that can be read at some of them. */
.page-inspect .beat-card {
  background: color-mix(in srgb, var(--bg) 76%, transparent);
  backdrop-filter: blur(26px) saturate(1.1);
}
.page-inspect .beat--take .beat-in {
  background: color-mix(in srgb, var(--bg) 80%, transparent);
}

/* ⭐ PER-CARD TUNING, because composition is decided shot by shot and legibility has
   to be decided the same way. A card that sits over sky needs less ground than the
   same card over a sunlit wall. Tune the ONE card that is struggling with data-veil
   rather than pushing every card darker to satisfy the worst of them, which is how a
   page ends up looking like a stack of boxes. */
.beat-card[data-veil="light"] { background: color-mix(in srgb, var(--bg) 58%, transparent); }
.beat-card[data-veil="heavy"] { background: color-mix(in srgb, var(--bg) 82%, transparent); }
.beat-card[data-veil="solid"] {
  background: var(--bg);
  backdrop-filter: none;
}

/* The hero has NO card by design: it is the one statement that should read as being ON
   the building rather than in a box, and boxing it would cost the page its best moment.
   So it gets a scrim instead, sized and faded so it never resolves into a shape — you
   see the building through it and simply find the words readable. */
.page-inspect .beat--hero::before {
  content: "";
  position: absolute; inset: -12vh -8vw;
  pointer-events: none; z-index: -1;
  background: radial-gradient(ellipse 62% 74% at 24% 50%,
    color-mix(in srgb, var(--bg) 92%, transparent) 0%,
    color-mix(in srgb, var(--bg) 60%, transparent) 46%,
    transparent 76%);
}
/* The sub-copy is the part that actually washed out — muted grey over lit brickwork.
   Deliberately NOT applied to the title: it carries a clipped gradient, and a shadow
   under background-clip:text renders as a smear rather than a halo. */
.page-inspect .beat--hero .hero-sub {
  text-shadow: 0 1px 16px color-mix(in srgb, var(--bg) 76%, transparent);
}

/* ── THE LEDGER — findings carried forward (ledger.js) ───────────────────────
   Same glass as the beat cards so the two read as a pair across the building, one
   making the argument and one showing it. Hairlines are the DATA hairline: gold is
   reserved for things you can operate, and nothing here is operable. */
/* ⚠️ NOT --hairline-data ITSELF. That token is white at 18% — right for a card border
   on flat charcoal, invisible as a leader line crossing sunlit brick and lawn. Still
   the DATA colour (the neutral text tone, never gold), lifted to 62%, and laid over a
   soft dark halo: the same device the app uses for grid labels, which is what lets a
   thin line read over a busy background without having to be a thick one. Both tones
   come from --text and --bg, so they flip with the theme. */
/* ⛔ ABOVE THE LEDGER, NOT UNDER IT. Drawn beneath, the glass swallowed every landing
   point the moment the frame went solid — the one reference that shows where each line
   connects. The lines end ON the frame's edge, so nothing of them crosses the glass. */
.ledger-lines {
  position: absolute; inset: 0; width: 100%; height: 100%;
  z-index: 4; pointer-events: none; overflow: visible;
  /* ⭐ A CASED LINE, the cartographer's answer: a near-white core inside a dark
     casing. Jeremy, 2026-09-11: the 62% grey vanished against brick and slate, which
     are mid-tones — no single brightness reads on all of them. The casing carries the
     contrast over a lit wall, the core carries it over dark sky, so the link from the
     table to the point in measurable space — the story here — reads over anything.
     Theme tokens, so light theme gets a dark core in a pale casing. */
  --ledger-ink:  color-mix(in srgb, var(--text) 94%, transparent);
  --ledger-halo: color-mix(in srgb, var(--bg) 78%, transparent);
}
.ledger-halo { fill: none; stroke: var(--ledger-halo); stroke-width: 4.2; stroke-linecap: round; stroke-linejoin: round; }
.ledger-line { fill: none; stroke: var(--ledger-ink); stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }
.ledger-ring-halo { fill: none; stroke: var(--ledger-halo); stroke-width: 4.2; }
.ledger-ring { fill: none; stroke: var(--ledger-ink); stroke-width: 1.4; }
/* The landing node is SOLID — the line's ink mixed into the ground, not faded over it —
   so it holds as a firm point on the frame whatever is behind it, glass or model. */
.ledger-tip {
  fill: color-mix(in srgb, var(--text) 78%, var(--bg));
  stroke: var(--ledger-halo); stroke-width: 2; paint-order: stroke;
}

/* ⛔ PLACED AGAINST THE PAGE'S COLUMN, NOT THE WINDOW'S EDGE.
   The story cards live in a centred 1320px column. Pinning the ledger to the viewport
   edge left it hugging the far left on a wide screen while the card sat well inside
   the page — lop-sided, and it read as an afterthought. It now sits a set fraction of
   the way from the window edge in toward that column: far enough in to balance the
   card, far enough out that the hairlines keep the length that makes them read as
   lines rather than ticks. On a narrow screen, where there is no gutter, it simply
   sits at the margin. One number to tune: --ledger-inset (0 = window edge, 1 = the
   column's edge, mirroring the card exactly). */
/* Position and size are set by ledger.js to MIRROR the card opposite — same inset
   from its side of the window, same width, at least the same height. The values here
   are only the fallback for a page with no card to mirror. */
.ledger {
  --ledger-inset: 1;
  position: absolute; z-index: 3;
  left: max(24px, calc((100vw - 1320px) / 2 * var(--ledger-inset) + 24px));
  top: 50%;
  transform: translateY(-50%);
  width: min(320px, 30vw);
  padding: var(--space-6) var(--space-6) var(--space-6) var(--space-5);
  visibility: hidden; pointer-events: none;
  display: flex; flex-direction: column;
}
/* ⭐ THE BOX IS ITS OWN LAYER, so it can be BUILT around the table rather than
   arriving with it: the lines draw out of the building, the rows appear where they
   land, and only then does the glass close in around them. Same glass as the beat
   cards, so the pair reads as one system either side of the building. */
.ledger-frame {
  position: absolute; inset: 0; z-index: 0;
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--bg) 76%, transparent);
  backdrop-filter: blur(26px) saturate(1.1);
  border: 1px solid color-mix(in srgb, var(--accent-line) 40%, transparent);
  box-shadow: var(--shadow-lg);
  opacity: 0;
}
.ledger > :not(.ledger-frame) { position: relative; z-index: 1; }
.ledger-chrome { opacity: 0; }
.ledger-kicker {
  margin: 0;
  font-family: var(--font-mono); font-size: var(--text-xs);
  letter-spacing: .18em; text-transform: uppercase; color: var(--text-muted);
}
/* Balanced, so a two-line lede breaks into two even lines rather than a full line and
   a stray word or two. */
.ledger-lede { margin: .45rem 0 var(--space-4); font-size: var(--text-sm); color: var(--text); text-wrap: balance; }
/* ⭐ THE SCHEDULE: ONE GRID, SHARED BY EVERY ROW. The header and all five rows are
   subgrids of .ledger-table, so each column is exactly as wide as its widest cell
   across the whole table and the columns line up to the pixel — five separate row
   grids would each size to their own content and drift apart.
     n · status · item · type · cost
   ⛔ ONE LINE PER ROW. Nothing wraps: status is held to the longest label the page
   uses (--ledger-status-ch, set by ledger.js from its STATUSES), and the item name,
   the one column that can give, ellipsises. */
.ledger-table {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr) auto auto;
  column-gap: .8rem;
}
.ledger-head, .ledger-row {
  display: grid; grid-column: 1 / -1; grid-template-columns: subgrid;
  align-items: center; white-space: nowrap;
}
@supports not (grid-template-columns: subgrid) {
  .ledger-head, .ledger-row { grid-template-columns: 2ch 13ch minmax(0, 1fr) 6ch 7ch; column-gap: .8rem; }
}
.ledger-head {
  padding-bottom: .5rem;
  font-family: var(--font-mono); font-size: 10px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--text-faint);
  border-bottom: 1px solid color-mix(in srgb, var(--hairline-data) 22%, transparent);
}
.ledger-rows { display: contents; list-style: none; margin: 0; padding: 0; }
.ledger-row {
  padding: .6rem 0;
  border-bottom: 1px solid color-mix(in srgb, var(--hairline-data) 11%, transparent);
  will-change: opacity, transform;
}
.ledger-n { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-faint); }
.ledger-status { display: flex; align-items: center; gap: .45rem; }
.ledger-status i { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.ledger-status b {
  font-family: var(--font-mono); font-size: 11px; font-weight: 400; color: var(--text);
  min-width: var(--ledger-status-ch, 11ch);      /* ch of THIS font: a fixed width, not a guess */
}
.ledger-item {
  min-width: 0; overflow: hidden; text-overflow: ellipsis;
  font-size: .8125rem; color: var(--text);
}
.ledger-type { font-family: var(--font-mono); font-size: 11px; color: var(--text-muted); }
/* Costs right-aligned in figures of equal width, the way a schedule of works totals. */
.ledger-cost, .ledger-head > :last-child { text-align: right; }
.ledger-cost { font-family: var(--font-mono); font-size: 11px; color: var(--text); font-variant-numeric: tabular-nums; }
/* A resolved cost is STRUCK, never zeroed — the figure stays on the record. The strike
   is drawn, left to right, by --strike (0 → 1) from ledger.js, so it runs backwards
   when the reader scrolls back. */
.ledger-cost em { font-style: normal; position: relative; display: inline-block; }
.ledger-cost em::after {
  content: ''; position: absolute; left: -2px; right: -2px; top: 52%;
  height: 1.2px; background: currentColor;
  transform: scaleX(var(--strike, 0)); transform-origin: left center;
}
.ledger-row { transition: none; }
.ledger-row.is-hidden .ledger-status { opacity: .5; }
.ledger-foot { margin: var(--space-4) 0 0; font-size: var(--text-xs); color: var(--text-muted); }

/* ── THE GRADING PROOF — the app's condition picker, under "Graded the way your
   client already reads" ─────────────────────────────────────────────────────────
   A faithful replica of dragoneye-app's capture control (AddAnnotationModal.jsx /
   .scss, conditionRating.js): same labels, order and traffic-light hexes, the pill's
   own anatomy (dot · bold label · muted headline), and the selected state as the app
   draws it — a tint of the colour with its solid border. Dressed in the site's glass
   so it reads as the card's companion, not a pasted screenshot.
   ⛔ FIXED ON SCREEN, not carried with the card: it fades into the spot under the card
   as the card floats past, and fades out again in that same spot. Position and fade
   come from inspect.js (paintGrading), on its OWN window, so it outlasts the card and
   hands over to the red findings. */
.grading {
  --g-green: #22c55e; --g-amber: #f59e0b; --g-red: #ef4444;
  position: fixed; z-index: 1;
  box-sizing: border-box;
  padding: var(--space-5) var(--space-6) var(--space-5) var(--space-5);
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--bg) 76%, transparent);
  backdrop-filter: blur(26px) saturate(1.1);
  border: 1px solid color-mix(in srgb, var(--accent-line) 40%, transparent);
  box-shadow: var(--shadow-lg);
  opacity: 0; visibility: hidden; pointer-events: none;
  display: grid; gap: .9rem;
  will-change: opacity, transform;
}
.grading-row { display: grid; grid-template-columns: 6.5rem minmax(0, 1fr); align-items: center; gap: .75rem; }
/* As the app lays it out: the Condition label centred against its stack of pills. */
.grading-label { font-size: var(--text-sm); color: var(--text); }
.grading-select {
  position: relative;
  display: block; padding: .55rem 2.2rem .55rem .8rem;
  font-size: var(--text-sm); color: var(--text);
  background: color-mix(in srgb, var(--text) 4%, transparent);
  border: 1px solid color-mix(in srgb, var(--text) 16%, transparent);
  border-radius: .5rem;
}
.grading-select i {           /* the select's chevron, drawn rather than a glyph */
  position: absolute; right: .9rem; top: 50%;
  width: .45rem; height: .45rem; margin-top: -.32rem;
  border-right: 1.5px solid var(--text-muted); border-bottom: 1.5px solid var(--text-muted);
  transform: rotate(45deg);
}
.grading-pills { display: grid; justify-items: start; gap: .45rem; }
.grading-pill {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .3rem .75rem;
  border-radius: 999px;
  font-size: var(--text-sm); line-height: 1.2; white-space: nowrap;
  background: color-mix(in srgb, var(--text) 4%, transparent);
  border: 1px solid color-mix(in srgb, var(--text) 14%, transparent);
}
.grading-pill b { width: .65rem; height: .65rem; border-radius: 50%; flex: none; }
.grading-pill strong { font-weight: 600; color: var(--text); }
.grading-pill em { font-style: normal; font-size: var(--text-xs); color: var(--text-muted); margin-left: .2rem; }
.grading-pill--green b { background: var(--g-green); }
.grading-pill--amber b { background: var(--g-amber); }
.grading-pill--red   b { background: var(--g-red); }
/* SELECTED, as the app draws it: the colour's tint and its solid border. --pick (0 → 1)
   is driven by scroll, so the reader watches Red being chosen as the control settles. */
.grading-pill--red {
  --pick: 0;
  background: color-mix(in srgb, var(--g-red) calc(var(--pick) * 18%), color-mix(in srgb, var(--text) 4%, transparent));
  border-color: color-mix(in srgb, var(--g-red) calc(var(--pick) * 100%), color-mix(in srgb, var(--text) 14%, transparent));
}

/* ── THE RICS CAVEAT ─────────────────────────────────────────────────────────────
   Quiet by design, never hidden: small muted type behind a white (data) hairline,
   fixed bottom-left while the grading is on screen. Legible, not decorative — it says
   something that matters, so it sits at a readable size and full contrast for its tone. */
.caveat {
  position: fixed; left: var(--space-6, 1.5rem); bottom: var(--space-6, 1.5rem); z-index: 2;
  max-width: 34ch; margin: 0;
  padding-left: .75rem;
  border-left: 1px solid color-mix(in srgb, var(--text) 30%, transparent);
  font-size: var(--text-xs); line-height: 1.5; color: var(--text-muted);
  text-shadow: 0 1px 8px color-mix(in srgb, var(--bg) 90%, transparent);
  opacity: 0; visibility: hidden; pointer-events: none;
}
@media (max-width: 700px) { .caveat { right: var(--space-6, 1.5rem); max-width: none; } }

/* ── THE REBRAND — "It goes out as your report" ⚗️ TRIAL ─────────────────────────
   The app's "Customise this report" dialog, cut to four fields, fills itself in with
   the scroll, and a miniature of the real report cover re-brands in step. The form
   wears the site's glass (it is the app's UI); the cover is PAPER, because it is the
   deliverable and must read as a document at a glance. Fixed on screen, placed and
   driven by inspect.js (paintRebrand). --rb-accent is set live from the swatch. */
.rebrand {
  --rb-accent: #b8892e;
  position: fixed; z-index: 1;
  display: flex; align-items: center; gap: 1.25rem;
  opacity: 0; visibility: hidden; pointer-events: none;
  will-change: opacity, transform;
}
@media (max-width: 1000px) { .rebrand { display: none; } }

.rb-form {
  width: 19rem; box-sizing: border-box;
  padding: var(--space-5);
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--bg) 80%, transparent);
  backdrop-filter: blur(26px) saturate(1.1);
  border: 1px solid color-mix(in srgb, var(--accent-line) 40%, transparent);
  box-shadow: var(--shadow-lg);
  display: grid; gap: .8rem;
}
.rb-head {
  display: flex; align-items: center; justify-content: space-between;
  padding-bottom: .7rem; margin-bottom: .1rem;
  border-bottom: 1px solid color-mix(in srgb, var(--text) 10%, transparent);
  font-size: var(--text-base); color: var(--text);
}
.rb-x { position: relative; width: .7rem; height: .7rem; }
.rb-x::before, .rb-x::after {
  content: ''; position: absolute; left: 50%; top: 0; width: 1.5px; height: 100%;
  background: var(--text-muted); transform: rotate(45deg);
}
.rb-x::after { transform: rotate(-45deg); }
.rb-field { display: grid; gap: .35rem; }
.rb-label { font-size: var(--text-xs); font-weight: 600; color: var(--text); }
.rb-label em { font-style: normal; font-weight: 400; color: var(--text-muted); }
.rb-input {
  /* A fixed height, so an EMPTY field is still a field. (Border-box: a min-height of
     1.2em included the padding, and an empty input collapsed to a sliver.) */
  display: block; box-sizing: border-box; height: 2.3rem; line-height: 1.3rem;
  padding: .45rem .65rem;
  font-size: var(--text-sm); color: var(--text); white-space: nowrap; overflow: hidden;
  background: color-mix(in srgb, var(--text) 4%, transparent);
  border: 1px solid color-mix(in srgb, var(--text) 16%, transparent);
  border-radius: .45rem;
}
.rb-input.is-typing, .rb-input.is-active { border-color: color-mix(in srgb, var(--accent) 70%, transparent); }
.rb-input.is-typing::after {           /* the caret, only while the field is being typed */
  content: ''; display: inline-block; width: 1.5px; height: 1em; margin-left: 1px;
  vertical-align: -.15em; background: var(--accent);
}
.rb-logo-row { display: flex; align-items: center; gap: .7rem; }
.rb-thumb {
  width: 3.2rem; height: 3.2rem; flex: none; border-radius: .45rem;
  display: grid; place-items: center;
  border: 1px dashed color-mix(in srgb, var(--text) 25%, transparent);
}
/* ⭐ THE LOGO CHANGES SHAPE, not just colour (Jeremy, 2026-09-11). Two deliberately
   generic marks — a square monogram and a round emblem, so the silhouette itself
   changes — stacked in one cell. The old one turns and shrinks away, THEN the new
   one turns and grows in: --rb-la (0 → 1, old out) and --rb-lb (0 → 1, new in) are
   set from scroll by inspect.js. Both take the accent via currentColor. Neither may
   ever look like somebody's real brand. */
.rb-logo-swap { display: grid; place-items: center; color: var(--rb-accent); }
.rb-logo-swap svg { grid-area: 1 / 1; width: 72%; height: 72%; overflow: visible; }
.rb-logo-a {
  opacity: calc(1 - var(--rb-la, 0));
  transform: rotate(calc(var(--rb-la, 0) * -90deg)) scale(calc(1 - var(--rb-la, 0) * .55));
}
.rb-logo-b {
  opacity: var(--rb-lb, 0);
  transform: rotate(calc((1 - var(--rb-lb, 0)) * 90deg)) scale(calc(.45 + var(--rb-lb, 0) * .55));
}
.rb-file { display: flex; align-items: center; min-width: 0; font-size: var(--text-xs); }
.rb-file b {
  font-weight: 500; padding: .35rem .55rem; border-radius: .35rem 0 0 .35rem;
  background: color-mix(in srgb, var(--text) 14%, transparent); color: var(--text);
}
.rb-fname {
  padding: .35rem .55rem; border: 1px solid color-mix(in srgb, var(--text) 14%, transparent);
  border-left: 0; border-radius: 0 .35rem .35rem 0; color: var(--text-muted);
  opacity: var(--rb-lb, 0);
}
.rb-colour { display: flex; gap: .5rem; align-items: stretch; }
.rb-swatch {
  width: 2.6rem; flex: none; border-radius: .45rem; background: var(--rb-accent);
  border: 1px solid color-mix(in srgb, var(--text) 16%, transparent);
}
.rb-hex { flex: 1; font-family: var(--font-mono); font-size: var(--text-xs); }
.rb-foot { display: flex; justify-content: flex-end; gap: .5rem; padding-top: .2rem; }
.rb-btn {
  padding: .4rem .9rem; border-radius: .45rem; font-size: var(--text-xs);
  border: 1px solid color-mix(in srgb, var(--text) 20%, transparent); color: var(--text);
}
.rb-save {
  border-color: transparent; color: var(--on-accent, #14110a);
  background: var(--accent);
  box-shadow: 0 0 0 calc(var(--rb-saved, 0) * 4px) color-mix(in srgb, var(--accent) 35%, transparent);
}

/* THE COVER — after the app's own: ReportPage.css (.report-cover*) and
   RicsCoverOverview.css, same fonts (Georgia; Helvetica for the capitals), same
   colours, same anatomy. It is sized to the screen height and every measurement
   inside is in cqw (a percentage of the cover's own width), so it scales exactly like
   a page rather than reflowing. The small type is set a little larger than a true
   scale model would give, so the parts that carry the point stay legible at 1080p. */
.rb-cover {
  --ink: #1c2024; --ink-soft: #4a5260; --sans: "Helvetica Neue", Arial, sans-serif;
  position: relative; overflow: hidden; flex: none;
  /* The real cover is a web page, not A4, and below its content it simply runs on.
     Cropped to 1 : 1.22 so the miniature is all cover and no empty page. */
  width: calc(min(60vh, 36rem) / 1.22); aspect-ratio: 1 / 1.22;
  container-type: inline-size;
  border-radius: .3rem;
  color: var(--ink);
  font-family: Georgia, "Times New Roman", serif;
  /* ⭐ SOLID WHERE THE CHANGE HAPPENS, DISSOLVING BELOW IT (Jeremy, 2026-09-11). The
     logo, company, title, "Prepared for" and accent all live in the top 44%, which
     stays at full strength; beneath it the page thins into the model, so the cover
     reads as lifted out of the scene rather than laid on top of it. The footer and
     attribution go with it — sacrificial, by his call: this is our own site. */
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 44%, rgba(0, 0, 0, .6) 64%, rgba(0, 0, 0, .15) 86%, transparent 100%);
          mask-image: linear-gradient(180deg, #000 0%, #000 44%, rgba(0, 0, 0, .6) 64%, rgba(0, 0, 0, .15) 86%, transparent 100%);
}
/* The shadow lives on a wrapper, as a drop-shadow of the MASKED page: a mask clips a
   box-shadow away entirely, and a solid shadow under a fading page would lie about
   its shape. This one fades with the paper. */
.rb-cover-wrap { flex: none; filter: drop-shadow(0 1.4rem 2.2rem rgba(0, 0, 0, .45)); }
/* The inspection image, treated as the app treats it: blurred, desaturated, zoomed so
   the edges never show. A snapshot of the live model, set by inspect.js; until then,
   the app's own tint. */
/* ⭐ THE PAPER IS A TOUCH TRANSLUCENT, THE WORDS ARE NOT (Jeremy, 2026-09-11): "clear
   it is still on top of a scrolling model, however it still needs to remain clear".
   Snapshot and white wash are one group at --rb-paper opacity, so the live model
   moves faintly through the page, while the content layer above it stays fully
   solid. Group opacity, not per-layer: two stacked 90% layers let almost nothing
   through, one 90% group lets through a steady 10%. */
.rbc-paper { position: absolute; inset: 0; opacity: var(--rb-paper, .9); background: #fff; }
.rbc-backdrop {
  position: absolute; inset: -6%;
  background: #e8e5de center / cover no-repeat;
  filter: blur(1.2cqw) saturate(.85) brightness(1.03);
  transform: scale(1.06);
}
.rbc-wash {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(255, 255, 255, .88) 0%, rgba(255, 255, 255, .7) 40%, rgba(255, 255, 255, .88) 100%);
}
.rbc-page {
  position: absolute; inset: 0; box-sizing: border-box;
  display: flex; flex-direction: column;
  padding: 6.5cqw 5.5cqw 4cqw;
}
.rbc-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6cqw; }
.rbc-logo { width: 10cqw; height: 10cqw; }
.rbc-variant {
  font: 600 2cqw/1.3 var(--sans); letter-spacing: .18em; text-transform: uppercase;
  color: var(--rb-accent); text-align: left;
}
.rbc-variant small { display: block; font-size: 1.7cqw; margin-top: .6cqw; opacity: .8; }
.rbc-eyebrow {
  font: 600 2.1cqw/1.2 var(--sans); letter-spacing: .18em; text-transform: uppercase;
  color: var(--rb-accent); min-height: 1.2em;
}
.rbc-title {
  font-weight: 700; font-size: 8.4cqw; line-height: 1.02; letter-spacing: -.025em;
  margin: 1.6cqw 0 5.5cqw;
  background: linear-gradient(180deg, rgba(28, 32, 36, .85) 0%, rgba(28, 32, 36, 1) 60%);
  -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;
}
.rbc-meta { display: grid; grid-template-columns: auto 1fr; gap: 1.3cqw 6cqw; margin: 0; align-items: baseline; }
.rbc-meta dt { font: 600 1.8cqw/1 var(--sans); letter-spacing: .14em; text-transform: uppercase; color: var(--ink-soft); }
.rbc-meta dd { margin: 0; font: 2.5cqw/1.2 var(--sans); color: var(--ink); white-space: nowrap; min-height: 1.2em; }
/* "Prepared for" only appears once it is filled, as on the real cover. */
.rbc-meta .rbc-for { opacity: var(--rb-for, 0); }
.rbc-tally { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 1.1cqw; margin-top: 5cqw; }
.rbc-tally span {
  display: flex; flex-direction: column; gap: .3cqw;
  padding: 1.5cqw 1.4cqw 1.7cqw; border-radius: .9cqw; border-left: .7cqw solid;
}
.rbc-tally b { font-size: 5.2cqw; font-weight: 800; line-height: 1; font-variant-numeric: tabular-nums; }
.rbc-tally .t-risk b, .rbc-tally .t-obs b { font-size: 4cqw; }
.rbc-tally em { font-style: normal; font-weight: 700; font-size: 1.7cqw; letter-spacing: .04em; text-transform: uppercase; white-space: nowrap; }
.rbc-tally .t-obs em { font-size: 1.4cqw; letter-spacing: .01em; }   /* the longest label, held inside its card */
.rbc-tally small { font-size: 1.5cqw; line-height: 1.3; color: #475569; }
.t-red   { background: #fee2e2; border-color: #ef4444; color: #dc2626; }
.t-amber { background: #fef3c7; border-color: #f59e0b; color: #d97706; }
.t-green { background: #dcfce7; border-color: #22c55e; color: #16a34a; }
.t-risk  { background: #fef3c7; border-color: #f59e0b; color: #92400e; }
.t-obs   { background: #dbeafe; border-color: #3b82f6; color: #1e40af; }
.rbc-property {
  display: flex; align-items: center; gap: 1.1cqw; margin-top: 2.6cqw;
  padding: 1.6cqw 1.9cqw; border-radius: .9cqw;
  background: rgba(255, 255, 255, .7); border: 1px solid rgba(15, 23, 42, .08);
}
.rbc-label { font: 700 1.55cqw/1 var(--sans); letter-spacing: .08em; text-transform: uppercase; color: #64748b; }
.rbc-property .rbc-label { margin-right: 1cqw; }
.rbc-pill { font-size: 1.75cqw; padding: .5cqw 1.4cqw; border-radius: 99px; background: #f1f5f9; border: 1px solid #e2e8f0; color: #1e293b; }
.rbc-pill--heritage { background: #fce7f3; color: #9d174d; border-color: #f9a8d4; font-weight: 600; }
.rbc-property + .rbc-label { margin: 3cqw 0 1.1cqw; }
.rbc-elements { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: .8cqw; }
.rbc-elements span {
  display: flex; flex-direction: column; gap: .3cqw; min-height: 8.5cqw;
  padding: 1.1cqw 1cqw; border-radius: .7cqw;
  background: #f8fafc; box-shadow: 0 1px 2px rgba(15, 23, 42, .06);
}
.rbc-elements b { font-size: 1.5cqw; line-height: 1.15; color: #0f172a; }
.rbc-elements em { font-style: normal; font-size: 1.25cqw; letter-spacing: .05em; text-transform: uppercase; color: #94a3b8; }
.rbc-elements small { font-size: 1.25cqw; color: #64748b; margin-top: auto; }
.rbc-elements .e-red   { background: #fee2e2; border-left: .5cqw solid #ef4444; }
.rbc-elements .e-red em { color: #dc2626; }
.rbc-elements .e-amber { background: #fef3c7; border-left: .5cqw solid #f59e0b; }
.rbc-elements .e-amber em { color: #d97706; }
.rbc-foot {
  display: flex; justify-content: space-between; align-items: baseline; margin-top: auto;
  font: 1.9cqw/1.2 var(--sans); color: var(--ink-soft);
}
.rbc-hint { font-weight: 600; font-size: 1.6cqw; letter-spacing: .18em; text-transform: uppercase; color: var(--rb-accent); }
/* ⭐ THE ATTRIBUTION STAYS — the settled white-label policy. The real rune asset, as
   the app sets it: "GENERATED WITH [rune] DRAGONEYE" in quiet capitals. */
.rbc-attr {
  display: flex; justify-content: center; align-items: center; gap: 1cqw; margin-top: 2.2cqw;
  font: 500 1.6cqw/1 var(--sans); letter-spacing: .12em; text-transform: uppercase; color: var(--ink-soft);
}
.rbc-attr img { width: 2.6cqw; height: auto; }

/* The hand-over beat sits over the CENTRE of the model, so the copy needs its own
   ground to stand on — gold type straight over sunlit foliage is unreadable. A glass
   panel keeps the quarry visible behind the words instead of hiding it. */
.beat--take .beat-in {
  /* ⛔ NOT --space-7: THERE IS NO 7 IN THE SCALE (1,2,3,4,5,6,8,10,12,16), and ONE bad
     value voids the WHOLE shorthand, so this card rendered with no padding at all — the
     kicker jammed on the top edge and the foot line on the bottom. Jeremy spotted it on
     screen, 2026-09-17. ⚠️ The same trap swallowed a --space-9 gap in the pricing band
     earlier the same day: an invalid token fails SILENTLY and looks like a design choice. */
  padding: var(--space-8) var(--space-8) var(--space-6);
  border-radius: var(--radius-xl);
  background: color-mix(in srgb, var(--bg) 66%, transparent);
  backdrop-filter: blur(22px) saturate(1.15);
  border: 1px solid color-mix(in srgb, var(--accent-line) 45%, transparent);
  box-shadow: var(--shadow-lg);
}

.take-title {
  margin: 0 0 var(--space-4);
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(2.2rem, 5vw, 4rem);
  letter-spacing: -0.02em;
  background: var(--accent-gradient);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.take-sub { max-width: 52ch; margin: 0 auto var(--space-6); color: var(--text-muted); font-size: var(--text-lg); }

.scroll-cue {
  position: absolute; bottom: var(--space-8); left: 50%; transform: translateX(-50%);
  width: 22px; height: 36px;
  border: 1px solid var(--accent-line); border-radius: var(--radius-pill);
}
.scroll-cue span {
  display: block; width: 3px; height: 7px; margin: 7px auto 0;
  border-radius: 2px; background: var(--accent);
  animation: cue 1.9s var(--ease-in-out) infinite;
}
@keyframes cue { 0% { opacity:0; transform: translateY(0); } 40% { opacity:1; } 80% { opacity:0; transform: translateY(12px); } 100% { opacity:0; } }

/* ── shell ───────────────────────────────────────────────────────────────── */
.nav, .foot { position: relative; z-index: 1; }
/* MAIN was the real event thief. It is a positioned box at z-index 1 covering the whole
   viewport, sitting directly over the canvas — so every drag and wheel hit <main> and
   died there. Making .story transparent to the pointer achieved nothing while its own
   PARENT was still catching everything. main is now transparent too, and the reading
   half of the page takes its events back explicitly. */
main { position: relative; z-index: 1; pointer-events: none; }
.content { pointer-events: auto; }
/* The reading half of the page. Opaque, so the pinned stage behind it never
   shows through the copy — and it rises over the quarry as you scroll out of
   the story, which is what makes the transition feel like one continuous move. */
.content {
  position: relative; z-index: 1;
  background: var(--bg);
  border-top: 1px solid var(--divider);
  box-shadow: 0 -40px 80px -20px rgba(0,0,0,.6);
  /* ⛔ THE SPACE ABOVE THE FOOTER LIVES IN HERE, NOT ON THE FOOTER. It used to be
     `margin-top: var(--space-12)` on .foot — 6rem of margin, which sits OUTSIDE both
     boxes and is therefore transparent. On these two pages the stage is
     `position: fixed` behind everything, so that band was a letterbox onto the 3D
     viewer: you scrolled off the last section and the building slid past behind a
     6rem window before the footer arrived. Same measurement, moved inside the opaque
     surface, so the reading half of the page now runs unbroken into the foot. */
  padding-bottom: var(--space-12);
}
:root[data-theme="light"] .content { box-shadow: 0 -40px 80px -30px rgba(0,0,0,.18); }
.foot { background: var(--bg); }
/* A parked band stays in the source but never renders. Explicit rather than relying on the
   UA default, matching how .explore / .readout / .vc-controls handle it elsewhere here. */
.band[hidden] { display: none; }

.band {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: var(--space-16) var(--space-6);
}
.band--rule { border-top: 1px solid var(--divider); }

/* ── nav ─────────────────────────────────────────────────────────────────── */
.nav {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-6);
  backdrop-filter: blur(14px);
  background: color-mix(in srgb, var(--bg) 76%, transparent);
  border-bottom: 1px solid var(--divider);
}
.brand { display: inline-flex; align-items: center; gap: .6rem; text-decoration: none; }
.rune { filter: drop-shadow(0 0 10px rgba(255,216,107,.28)); }
:root[data-theme="light"] .rune { filter: none; }
/* ⛔ THE WORDMARK IS NOT GOLD. Jeremy, 2026-09-17: the brand settled on a TWO-TONE
   lockup — gold mark, bone wordmark on charcoal; bronze mark, ink wordmark on bone. It
   had been filled with --accent-gradient here, which made the whole lockup one gold mass
   and is the version the Academy moved away from.
   ⭐ NOT AN APPROXIMATION OF THE BRAND MARK: --text resolves to #f3f1ec on dark, which is
   byte-exact the wordmark colour in dragoneye-academy/tools/export-logo.mjs, and to
   --char-900 ink on light, which is the same role in that file's light colourway. The
   authority is the real export routine, not a sampled screenshot. */
.wordmark {
  font-family: var(--font-wordmark);
  font-size: 1.5rem;
  letter-spacing: .02em;
  color: var(--text);
}
.nav-right { display: flex; align-items: center; gap: var(--space-3); }

.theme-toggle {
  display: inline-flex; align-items: center; gap: .45rem;
  background: transparent; cursor: pointer;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-pill);
  padding: .38rem .7rem;
  color: var(--text-muted);
  font-size: var(--text-xs);
  font-family: var(--font-mono);
  letter-spacing: .08em; text-transform: uppercase;
  transition: color var(--dur-fast), border-color var(--dur-fast);
}
.theme-toggle:hover { color: var(--text); border-color: var(--accent-line); }
.theme-dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 8px var(--accent);
}

/* ── buttons ─────────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  padding: .62rem 1.1rem;
  border-radius: var(--radius-pill);
  font-weight: 600;
  font-size: var(--text-sm);
  text-decoration: none;
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-base), filter var(--dur-fast);
  white-space: nowrap;
}
.btn:hover { transform: translateY(-1px); }
.btn-gold {
  background: var(--accent-cta);
  color: var(--on-accent);
  box-shadow: var(--shadow-sm);
}
.btn-gold:hover { box-shadow: var(--glow-accent), var(--shadow-md); filter: brightness(1.04); }
.btn-ghost { color: var(--text-muted); border-color: var(--border-subtle); }
.btn-ghost:hover { color: var(--text); border-color: var(--accent-line); }
.btn-line { color: var(--accent); border-color: var(--accent-line); background: transparent; }
.btn-line:hover { background: var(--accent-wash); }
.btn-lg { padding: .85rem 1.6rem; font-size: var(--text-base); }
.btn-block { width: 100%; padding: .95rem 1.6rem; font-size: var(--text-base); }

/* ── hero ────────────────────────────────────────────────────────────────── */
.hero {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: clamp(3rem, 7vw, 6rem) var(--space-6) var(--space-12);
  text-align: center;
}
.eyebrow {
  display: inline-flex; align-items: center; gap: .55rem;
  margin: 0 0 var(--space-5);
  padding: .35rem .85rem;
  border: 1px solid var(--hairline-tool);
  border-radius: var(--radius-pill);
  background: var(--accent-wash);
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: .12em; text-transform: uppercase;
}
:root[data-theme="light"] .eyebrow { color: var(--bronze-strong); }
.pip {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 8px var(--accent);
  animation: pulse 2.4s var(--ease-in-out) infinite;
}
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }

.hero-title {
  margin: 0 0 var(--space-5);
  font-family: var(--font-display);
  font-weight: 800;
  /* ⛔ SIZED AGAINST THE MEASURE, NOT THE SCREEN. At 5.25rem inside a 52ch column
     every sentence of the headline broke three ways: six lines at 1080, the first
     one clipped by the header and the buttons clipped off the bottom. The cap comes
     down and the column goes out, so each sentence takes two lines and the whole
     hero — eyebrow, title, summary, both buttons — fits one 1080 screen. */
  font-size: clamp(2.3rem, 4.4vw, 4.2rem);
  line-height: var(--leading-tight);
  letter-spacing: -0.02em;
}
.hero-title em {
  font-style: normal;
  background: var(--accent-gradient);
  -webkit-background-clip: text; background-clip: text;
  color: transparent;
}
.hero-sub {
  max-width: 62ch; margin: 0 auto var(--space-8);
  color: var(--text-muted);
  font-size: clamp(1.05rem, 1.5vw, 1.375rem);
}
.hero-cta { display: flex; gap: var(--space-3); justify-content: center; flex-wrap: wrap; }
.hero-proof {
  list-style: none; margin: var(--space-8) 0 0; padding: 0;
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: var(--space-3) var(--space-6);
  color: var(--text-faint);
  font-size: var(--text-sm);
}
.hero-proof strong { color: var(--text); font-weight: 600; }
.hero-proof li { display: flex; align-items: center; gap: .5rem; }
.hero-proof li::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%;
  background: var(--accent-deep);
}

/* ── media slots (swap the inner for <video>/<img>) ──────────────────────── */
.media { margin: 0; }
.media-inner {
  position: relative;
  aspect-ratio: 16 / 9;
  border: 1px solid var(--hairline-data);
  border-radius: var(--radius-lg);
  background:
    radial-gradient(120% 90% at 50% 0%, var(--accent-wash) 0%, transparent 60%),
    var(--card-tool-bg);
  display: grid; place-items: center;
  overflow: hidden;
  box-shadow: var(--shadow-md);
}
.media-inner::after {
  content: ""; position: absolute; inset: 0;
  background-image:
    linear-gradient(var(--grid-line) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
  background-size: 34px 34px;
}
.media-hint {
  position: relative; z-index: 1;
  color: var(--text-faint);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: .14em; text-transform: uppercase;
  text-align: center; padding: 0 1rem;
}
.media-cap {
  margin-top: .7rem;
  color: var(--text-faint);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: .06em;
}
.media-hero { margin-top: var(--space-12); }
.media-hero .media-inner { aspect-ratio: 16 / 8; }

/* ── section heads ───────────────────────────────────────────────────────── */
.band-head { max-width: 74ch; margin: 0 auto var(--space-10); text-align: center; }
.kicker {
  margin: 0 0 var(--space-3);
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-caps); text-transform: uppercase;
}
:root[data-theme="light"] .kicker { color: var(--bronze-strong); }
.band-head h2 {
  margin: 0 0 var(--space-4);
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(1.9rem, 3.4vw, 3rem);
  line-height: var(--leading-snug);
  letter-spacing: -0.015em;
}
.lede { margin: 0 auto; max-width: var(--maxw-prose); color: var(--text-muted); font-size: var(--text-lg); }

/* ── the pricing band reads LEFT ───────────────────────────────────────── */
/* Jeremy, 2026-09-17: centred stops working once the band head carries this much prose.
   Scoped to #cost on purpose — every other band on the page keeps its centred head. The
   copy starts at the band's own left edge, so it lines up with the first card below it.
   The last rule only exists because .lede zeroes its own margins to sit alone under a
   head; six paragraphs would otherwise butt together into one slab. */
#cost .band-head { text-align: left; margin-left: 0; margin-right: auto; }
#cost .lede { margin-left: 0; margin-right: 0; }
#cost .lede + .lede { margin-top: var(--space-4); }
/* ⛔ THE FINE PRINT IS WIDER HERE, AND ONLY HERE. The shared .fineprint is capped at
   --maxw-prose (68ch) and centred, which inside a 1440px band turns four sentences into
   five short squat lines. A footnote is not prose and does not want a reading measure;
   it wants to be got through. Widened for this band alone, so nothing else on the site
   inherits it. */
#cost .fineprint { max-width: 108ch; }
#cost .fineprint + .fineprint { margin-top: var(--space-4); }
/* Why the figure is not printed. A hairline separates it from the offer, so it reads as
   a note about the price rather than part of what you get. Data, not a tool. */
#cost .cost-why {
  margin: var(--space-4) 0 0; padding-top: var(--space-4);
  border-top: 1px solid var(--hairline-data);
  color: var(--text-faint); font-size: var(--text-xs); line-height: 1.6;
}
/* Jeremy's lead-ins read as sub-headings, so they are marked rather than emphasised.
   ⛔ Not bold: that competes with the headline. ⛔ Not the mono uppercase used for
   kickers and card labels either — "PAY ONLY FOR WHAT YOU DO:" shouted mid-paragraph
   stops the read dead. Body typeface, gold, a little heavier. */
#cost .lede-lead { color: var(--accent); font-weight: 650; }

/* Copy left, figures right. Collapses back to a single column below 1100px, where the
   screen is not wide enough for two and the stack is the right answer again. */
#cost .cost-layout {
  display: grid;
  /* ⭐ THE LEFT TRACK IS THE MEASURE, NOT A FRACTION. .band-head carries max-width:74ch
     from the base rule, so a 1fr track left 200px of dead space between the copy's right
     edge and the gap, and the boxes read as detached. Sizing the track TO the measure and
     giving the remainder to the boxes closes it to exactly the gap. Wider cards also wrap
     their notes in two lines instead of three, so the column gets shorter with it. */
  grid-template-columns: minmax(0, 74ch) minmax(320px, 1fr);
  gap: var(--space-8);   /* ⛔ there is no --space-9 in tokens.css; the scale skips 7 and 9 */
  align-items: start;
}
@media (max-width: 1100px) { #cost .cost-layout { grid-template-columns: 1fr; gap: var(--space-8); } }
/* Stacked in the right-hand track. auto-fit would try to fit three narrow columns in
   there and shatter the figures. Tighter inset than the row version, because stacked
   the card padding is paid three times down the column. */
#cost .cost-grid { grid-template-columns: 1fr; gap: var(--space-4); }
#cost .cost-card { padding: var(--space-5); }
.body { color: var(--text-muted); }

/* ── cost band ───────────────────────────────────────────────────────────── */
.cost-grid {
  display: grid; gap: var(--space-5);
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}
.cost-card {
  padding: var(--space-6);
  border: 1px solid var(--hairline-data);
  border-radius: var(--radius-lg);
  background: var(--card-bg);
}
.cost-card--ours {
  border-color: var(--hairline-tool);
  background: var(--card-tool-bg);
  box-shadow: var(--shadow-md);
  position: relative;
}
.cost-card--ours::before {
  content: ""; position: absolute; inset: -1px; border-radius: var(--radius-lg);
  padding: 1px; background: var(--accent-gradient);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
  opacity: .55; pointer-events: none;
}
.cost-label {
  margin: 0 0 var(--space-3);
  color: var(--text-faint);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: .12em; text-transform: uppercase;
}
.cost-fig {
  margin: 0 0 var(--space-3);
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(1.8rem, 2.8vw, 2.4rem);
  letter-spacing: -0.02em;
  line-height: 1.15;
}
.cost-fig span { color: var(--text-faint); font-weight: 400; margin: 0 .1em; }
.cost-fig em {
  font-style: normal; font-weight: 500;
  font-size: var(--text-base); color: var(--text-faint);
  font-family: var(--font-mono);
}
.cost-card--ours .cost-fig {
  background: var(--accent-gradient);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.cost-note { margin: 0; color: var(--text-muted); font-size: var(--text-sm); }
.fineprint {
  margin: var(--space-8) auto 0; max-width: var(--maxw-prose);
  text-align: center; color: var(--text-faint); font-size: var(--text-xs);
}

/* ── what we left out ────────────────────────────────────────────── */
/* Its own grid rather than .feat-grid: that one is pinned to four columns above 1800px
   for its six cards, and three cards in a four-column track leaves a hole at 4K. */
.more-grid {
  display: grid; gap: var(--space-5);
  grid-template-columns: repeat(auto-fit, minmax(290px, 1fr));
}
/* ⭐ THE SAME TREATMENT AS .feat, DELIBERATELY. Jeremy, 2026-09-17: these sit directly
   under the six feature cards, and six gold-outlined cards followed by three white ones
   reads as a distinction nobody meant. Gold frame, gradient fill, gold heading, so the
   two bands are one family.
   ⚠️ This is the second half of dropping the app's tool/data grammar from the content
   bands. See the note on .feat. */
.more-card {
  padding: var(--space-6);
  border: 1px solid var(--hairline-tool);
  border-radius: var(--radius-lg);
  background: var(--card-tool-bg);
}
.more-card h3 {
  margin: 0 0 var(--space-4);
  color: var(--accent);
  font-family: var(--font-display); font-size: var(--text-xl); font-weight: 700;
}
:root[data-theme="light"] .more-card h3 { color: var(--bronze-strong); }
.more-card ul { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--space-3); }
/* The tick is the banners' tick, drawn rather than a glyph, for the reason given there:
   a font's check mark is a different weight in every family and never sits on the
   baseline you want. One tick on the page, not two. */
.more-card li {
  position: relative; padding-left: 26px;
  color: var(--text-muted); font-size: var(--text-sm); line-height: 1.5;
}
.more-card li::before {
  content: ""; position: absolute; left: 2px; top: 0.36em;
  width: 10px; height: 6px;
  border-left: 2px solid var(--accent); border-bottom: 2px solid var(--accent);
  transform: rotate(-45deg); opacity: .9;
}
.more-foot {
  margin: var(--space-8) auto 0; max-width: var(--maxw-prose);
  text-align: center; color: var(--text-muted); font-size: var(--text-lg);
}
.more-foot a { color: var(--accent); text-decoration: none; border-bottom: 1px solid currentColor; }
.more-foot a:hover { opacity: .8; }

/* ── features ────────────────────────────────────────────────────────────── */
.feat-grid {
  display: grid; gap: var(--space-5);
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
/* ⭐ ALL SIX CARDS ARE GOLD-OUTLINED. Jeremy, 2026-09-17: "we are the gold standard
   now... the outline needs to be on all 6 too". Three used to take the white data
   hairline and a flat fill, three the gold tool hairline and a gradient.
   ⚠️ THE BACKGROUND HAD TO COME WITH THE BORDER. Moving the outline alone would have
   left six matching gold frames over two different fills, which reads as a mistake
   rather than a choice.
   ⚠️ The tool/data split is the APP's grammar (gold = a tool, white = data) carried
   onto the site. It is deliberately not applied in this band any more. The .tool and
   .data classes remain in the markup but no longer change anything here. */
.feat {
  padding: var(--space-6);
  border-radius: var(--radius-lg);
  background: var(--card-tool-bg);
  border: 1px solid var(--hairline-tool);
  transition: transform var(--dur-base) var(--ease-out), border-color var(--dur-base);
}
.feat:hover { transform: translateY(-3px); }
.feat h3 {
  margin: 0 0 var(--space-3);
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 700;
}
/* ⭐ ALL SIX HEADINGS ARE GOLD, not just the three "tool" cards. Jeremy, 2026-09-17:
   "we are the gold standard now". ⚠️ The gold/white split was carrying the app's own
   grammar onto the site — gold means a tool, white means a data property. That reading
   now rests on the card BORDER alone, which is quieter but still there. Said once,
   decided by Jeremy, recorded here so nobody "restores" it as a bug. */
.feat h3 { color: var(--accent); }
:root[data-theme="light"] .feat h3 { color: var(--bronze-strong); }
.feat p { margin: 0; color: var(--text-muted); font-size: var(--text-sm); }

/* ── rigour ──────────────────────────────────────────────────────────────── */
.rigour {
  display: grid; gap: var(--space-8);
  grid-template-columns: 1.15fr .85fr;
  align-items: start;
}
.rigour-copy h2 {
  margin: 0 0 var(--space-4);
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(1.8rem, 3vw, 2.6rem);
  line-height: var(--leading-snug); letter-spacing: -0.015em;
}
/* ONE MEASURE for every line of prose in this column. Long copy was running to ~95 characters,
   which is what made a well-set section read as unruly — the eye loses the line-return. Cap it
   at a book measure and the whole band tidies up without touching a single word. */
.rigour-copy .lede,
.rigour-copy .body,
.rigour-copy .formats-note { max-width: 62ch; }

.rigour-copy .lede { margin: 0 0 var(--space-5); }
.rigour-copy .body { margin: 0 0 var(--space-4); font-size: var(--text-sm); }
.rigour-copy em { color: var(--text); font-style: italic; }

/* The two closing takeaways are titled beats, not bold run-ins — the paragraph above them now
   opens plainly, so without a label the lower half reads as one grey slab. Deliberately the
   SAME family and size as the body it heads: the only signals are weight and colour. Adding a
   third type treatment here is what made the section look like four different fonts. */
.rigour-copy .body-lead {
  display: block;
  margin-bottom: .25rem;
  color: var(--text);
  font-weight: 700;
}

/* Import formats — read as instrument labels, not marketing badges.
   Laid out on a GRID rather than a wrapping flex row: free wrapping stranded "CSV points"
   alone on a second line, which is what read as lop-sided. A grid fills every row evenly and
   gives the set the tidy look of a spec sheet, which is the intent anyway. */
.formats {
  list-style: none;
  display: flex; flex-wrap: wrap;
  gap: .45rem;
  max-width: 62ch;                 /* same measure as the prose it sits between */
  margin: var(--space-5) 0 var(--space-5); padding: 0;
}
/* `flex: 1 1 auto` is the whole trick: every row GROWS to fill the width, so a short final
   row reads as deliberate rather than as one stranded pill. Free-wrapping fixed-width pills
   are what left "CSV points" orphaned on its own line. */
.formats li {
  flex: 1 1 auto;
  display: flex; align-items: center; justify-content: center;
  padding: .38rem .6rem;
  border: 1px solid var(--hairline-tool);
  border-radius: var(--radius-sm);
  background: var(--accent-wash);
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: .04em;           /* was .08em — it fought the mono kicker for attention */
  white-space: nowrap;
  text-align: center;
}
:root[data-theme="light"] .formats li { color: var(--bronze-strong); }
.formats-note {
  margin: 0 0 var(--space-4);
  color: var(--text-faint);
  font-size: var(--text-sm);
  border-left: 2px solid var(--accent-line);
  padding-left: var(--space-4);
}

.rigour-panel {
  padding: var(--space-6);
  border: 1px solid var(--hairline-tool);
  border-radius: var(--radius-lg);
  background: var(--card-tool-bg);
  box-shadow: var(--shadow-md);
}
.panel-label {
  margin: 0 0 var(--space-4);
  color: var(--accent);
  font-family: var(--font-mono); font-size: var(--text-xs);
  letter-spacing: var(--tracking-caps); text-transform: uppercase;
}
:root[data-theme="light"] .panel-label { color: var(--bronze-strong); }
.panel-rows { margin: 0 0 var(--space-5); }
.panel-rows > div {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-4);
  padding: .6rem 0;
  border-bottom: 1px solid var(--divider);
}
.panel-rows > div:last-child { border-bottom: 0; }
.panel-rows dt { color: var(--text-muted); font-size: var(--text-sm); }
.panel-rows dd {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--text-base);
  font-weight: 600;
}
.panel-rows dd.ok { color: var(--ok); }
/* The headline figure. Deliberately NOT green: in the app, cut is red and fill is
   blue, and a marketing page must not contradict the product's own language. */
.panel-rows dd.fig {
  background: var(--accent-gradient);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  font-size: var(--text-lg);
}
.panel-foot { margin: 0; color: var(--text-faint); font-size: var(--text-xs); }

/* ── proof grid ──────────────────────────────────────────────────────────── */
.proof-grid {
  display: grid; gap: var(--space-5);
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}

/* ── register ────────────────────────────────────────────────────────────── */
.band--register { border-top: 1px solid var(--divider); }
.reg {
  display: grid; gap: var(--space-10);
  grid-template-columns: 1fr 1fr;
  align-items: start;
}
.reg-copy h2 {
  margin: 0 0 var(--space-4);
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(1.9rem, 3.2vw, 2.8rem);
  line-height: var(--leading-snug); letter-spacing: -0.015em;
}
.reg-points { list-style: none; margin: var(--space-6) 0 0; padding: 0; }
.reg-points li {
  display: flex; align-items: flex-start; gap: .6rem;
  padding: .45rem 0; color: var(--text-muted); font-size: var(--text-sm);
}
.reg-points li::before {
  content: "✓"; color: var(--accent); font-weight: 700; line-height: 1.5;
}
:root[data-theme="light"] .reg-points li::before { color: var(--bronze-strong); }

.reg-form {
  padding: var(--space-6);
  border: 1px solid var(--hairline-tool);
  border-radius: var(--radius-xl);
  background: var(--card-tool-bg);
  box-shadow: var(--shadow-lg);
}
.row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); }
.field { display: block; margin-bottom: var(--space-4); }
.field > span {
  display: block; margin-bottom: .4rem;
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: .1em; text-transform: uppercase;
}
.field i { color: var(--accent); font-style: normal; }
.field input, .field select, .field textarea {
  width: 100%;
  padding: .7rem .85rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-subtle);
  background: var(--surface);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  transition: border-color var(--dur-fast), box-shadow var(--dur-fast);
}
.field textarea { resize: vertical; }
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-wash);
}
/* :user-invalid — NOT :invalid. A required field is "invalid" the moment the page
   loads, and greeting a visitor with red boxes they haven't touched is a good way
   to lose them. This only fires once they've actually interacted. */
.field input:user-invalid { border-color: var(--danger); }

.consent {
  display: flex; align-items: flex-start; gap: .6rem;
  margin: var(--space-2) 0 var(--space-5);
  color: var(--text-faint); font-size: var(--text-xs);
}
.consent input { margin-top: .2rem; accent-color: var(--accent-deep); }

.form-note { margin: var(--space-4) 0 0; text-align: center; font-size: var(--text-sm); min-height: 1.2em; }
.form-note.ok { color: var(--ok); }
.form-note.err { color: var(--danger); }

/* ── footer ──────────────────────────────────────────────────────────────── */
/* ⚠️ NO margin-top. See .content's padding-bottom above — a margin here is a hole. */
.foot { border-top: 1px solid var(--divider); }
.foot-in {
  max-width: var(--maxw); margin: 0 auto;
  padding: var(--space-8) var(--space-6);
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-4); flex-wrap: wrap;
}
.foot-note {
  margin: 0; color: var(--text-faint);
  font-family: var(--font-mono); font-size: var(--text-xs);
  letter-spacing: .14em; text-transform: uppercase;
}
.foot-legal { margin: 0; color: var(--text-faint); font-size: var(--text-xs); }

/* ── reveal on scroll ─────────────────────────────────────────────────────
   Progressive enhancement ONLY. The hidden state is gated behind .js, which
   app.js sets on <html> before paint. If JS never runs (blocked, failed, old
   browser) the page renders fully visible rather than blank — this is the one
   page that has to convert, so it must never depend on script to be readable. */
.js .reveal { opacity: 0; transform: translateY(18px); }
.js .reveal.in {
  opacity: 1; transform: none;
  transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out);
}

/* ── responsive (must read at 1080p AND 4K) ──────────────────────────────── */
@media (max-width: 900px) {
  .rigour, .reg { grid-template-columns: 1fr; }
  .row { grid-template-columns: 1fr; }
  .nav { padding: var(--space-3) var(--space-4); }
  .nav-right .btn-ghost { display: none; }
  .band { padding: var(--space-12) var(--space-5); }
}
@media (min-width: 1800px) {
  .band, .hero, .foot-in { max-width: 1440px; }

  /* SIX cards into FOUR columns leaves the last two hanging off the left edge. Only happens
     here: below 1800px the band is narrower, the grid resolves to three columns, and 3+3 is
     already symmetrical. So pin four columns and start the fifth card in column two — the
     bottom pair then sits centred beneath the middle of the row above. */
  .feat-grid { grid-template-columns: repeat(4, 1fr); }
  .feat-grid > :nth-child(5) { grid-column: 2; }
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .reveal, .reveal.in { opacity: 1; transform: none; transition: none; }
  .pip { animation: none; }
  .btn:hover, .feat:hover { transform: none; }
}

/* ════════════════════════════════════════════════════════════════════════════
   THREE-TAB EVOLUTION — the shared section tabs, plus the Inspect and The Case
   pages. Survey (index.html) is untouched but for the tab strip in its header.
   Same tokens, same dark stage, same engage-to-explore convention throughout.
   ════════════════════════════════════════════════════════════════════════════ */

/* section tabs (Survey / Inspect / The Case) — a segmented control in the nav */
.tabs {
  display: flex; align-items: center; gap: 2px;
  padding: 3px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-subtle);
  background: color-mix(in srgb, var(--surface) 55%, transparent);
  backdrop-filter: blur(8px);
}
.tab {
  padding: .44rem clamp(.7rem, 1.1vw, 1.05rem);
  border-radius: var(--radius-pill);
  font-family: var(--font-sans); font-weight: 600; font-size: var(--text-sm);
  color: var(--text-muted); text-decoration: none;
  letter-spacing: .01em; white-space: nowrap;
  transition: color var(--dur-fast), background var(--dur-fast);
}
.tab:hover { color: var(--text); }
.tab.is-active {
  color: var(--accent);
  background: var(--surface-raised);
  box-shadow: var(--shadow-sm);
}
:root[data-theme="light"] .tab.is-active { color: var(--bronze-strong); }

/* shared: The Case page fills the viewport and floats over the stage. Inspect
   now mirrors Survey (a scroll story on a fixed stage), so it is NOT flexed. */
body.page-case { min-height: 100vh; display: flex; flex-direction: column; }
.case-main { position: relative; z-index: 1; flex: 1; pointer-events: none; }
.foot--over { background: transparent; border-top: 0; }
.foot--over .foot-in { opacity: .82; }

/* INSPECT — the engaged HUD, and the dossier band on solid ground below the story */
.inspect-hud {
  position: fixed; right: var(--space-6); bottom: var(--space-6); z-index: 6;
  align-items: flex-end; margin: 0;
}

/* The real dossier this inspection carries, shown as a data band (gold = a tool,
   white = data — this is the record, so it reads as a premium tool card). */
.dossier--band {
  max-width: 760px; margin: 0 auto;
  padding: clamp(1.6rem, 3vw, var(--space-8));
  border-radius: var(--radius-lg);
  border: 1px solid var(--hairline-tool);
  background: var(--card-tool-bg);
  box-shadow: var(--shadow-md);
}
.dossier-figs {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: var(--space-5); margin: 0 0 var(--space-5);
}
.dossier--band .dossier-figs > div {
  display: flex; flex-direction: column-reverse; align-items: center; gap: .2rem; text-align: center;
}
.dossier-figs dt { font-size: var(--text-sm); color: var(--text-muted); }
.dossier-figs dd {
  margin: 0;
  font-family: var(--font-mono); font-weight: 600; font-size: clamp(2rem, 4vw, 3rem);
  color: var(--accent); line-height: 1;
}
:root[data-theme="light"] .dossier-figs dd { color: var(--bronze-strong); }
.dossier-foot {
  margin: 0; text-align: center;
  font-size: var(--text-sm); color: var(--text-faint); line-height: 1.5;
}

/* THE CASE — persona switch over the reused quarry, the argument reshapes */
.case-main { display: flex; flex-direction: column; }
.case-head { text-align: center; padding: clamp(1.25rem, 4vh, 2.6rem) var(--space-6) 0; }
.case-title {
  margin: .3rem 0 .4rem;
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(2.1rem, 4vw, 3.2rem); letter-spacing: -0.02em; line-height: 1.05;
}
.case-lede { margin: 0 auto var(--space-5); max-width: 46ch; color: var(--text-muted); font-size: var(--text-lg); }

.persona-switch {
  pointer-events: auto;
  display: inline-flex; gap: .4rem; flex-wrap: wrap; justify-content: center;
  padding: 4px; border-radius: var(--radius-pill);
  border: 1px solid var(--border-subtle);
  background: color-mix(in srgb, var(--surface) 55%, transparent);
  backdrop-filter: blur(10px);
}
.persona {
  display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
  padding: .5rem 1rem; border-radius: var(--radius-pill);
  background: transparent; border: 0; cursor: pointer; text-align: left;
  color: var(--text-muted);
  transition: color var(--dur-fast), background var(--dur-fast);
}
.persona-tab { font-family: var(--font-display); font-weight: 700; font-size: var(--text-base); }
.persona-role { font-family: var(--font-mono); font-size: 10px; letter-spacing: .1em; text-transform: uppercase; opacity: .82; }
.persona:hover { color: var(--text); }
.persona.is-active { color: var(--on-accent); background: var(--accent-cta); box-shadow: var(--shadow-sm); }

/* Floating hooks — they re-rank and re-skin when you switch persona. */
.case-hooks {
  position: fixed; right: clamp(1rem, 4vw, 3.5rem); top: 50%;
  transform: translateY(-50%);
  z-index: 2; pointer-events: none;
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: .6rem; align-items: flex-end;
  max-width: min(34ch, 42vw);
  transition: opacity var(--dur-fast);
}
.case-hooks.is-swapping { opacity: 0; }
.hook {
  padding: .6rem .9rem; border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--bg) 70%, transparent);
  backdrop-filter: blur(12px);
  border: 1px solid var(--hairline-data);
  color: var(--text-muted); font-size: var(--text-sm); text-align: right;
  opacity: 0; transform: translateX(16px);
  animation: hookIn var(--dur-base) var(--ease-out) forwards;
  animation-delay: calc(var(--i) * 90ms);
}
.hook.is-lead {
  color: var(--text);
  border-color: var(--hairline-tool);
  background: color-mix(in srgb, var(--bg) 62%, transparent);
  box-shadow: inset 0 0 0 1px var(--accent-line), var(--glow-accent);
  font-family: var(--font-display); font-weight: 700; font-size: var(--text-lg);
}
@keyframes hookIn { to { opacity: 1; transform: none; } }

/* The lead argument — WHY, then how, then the one CTA. Swaps with the persona. */
.case-lead {
  pointer-events: auto;
  position: relative; z-index: 3;
  margin: auto 0 clamp(1.25rem, 4vh, 2.6rem) clamp(1.25rem, 6vw, 6rem);
  width: clamp(20rem, 42vw, 34rem);
  padding: clamp(1.4rem, 2.2vw, 2.2rem);
  border-radius: var(--radius-lg);
  border: 1px solid var(--hairline-tool);
  background: color-mix(in srgb, var(--bg) 76%, transparent);
  backdrop-filter: blur(20px);
  box-shadow: var(--shadow-lg);
  transition: opacity var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out);
}
.case-lead.is-swapping { opacity: 0; transform: translateY(10px); }
.lead-role {
  margin: 0 0 .5rem;
  font-family: var(--font-mono); text-transform: uppercase;
  letter-spacing: .14em; font-size: var(--text-xs); color: var(--accent);
}
:root[data-theme="light"] .lead-role { color: var(--bronze-strong); }
.lead-why {
  margin: 0 0 var(--space-4);
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(1.5rem, 2.4vw, 2.1rem); line-height: 1.14; letter-spacing: -0.01em;
}
.lead-how { margin: 0 0 var(--space-5); color: var(--text-muted); font-size: var(--text-base); line-height: var(--leading-body); }
.lead-cta { display: flex; flex-direction: column; gap: .6rem; align-items: flex-start; }
.lead-note { margin: 0; font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-faint); }

/* small screens — the 3D stage is off; the argument still stands alone */
@media (max-width: 820px) {
  .case-lead {
    width: auto; margin: var(--space-6) var(--space-4);
    background: color-mix(in srgb, var(--bg) 90%, transparent);
  }
  .case-hooks {
    position: static; transform: none; margin: var(--space-5) var(--space-4) 0;
    max-width: none; align-items: stretch;
  }
  .hook, .hook.is-lead { text-align: left; }
  .case-lead { margin-top: var(--space-5); }
  .persona { align-items: center; text-align: center; }
}

/* stage loading state — a quiet marker while the tiles arrive, gone the moment
   the model is on screen. Only the Inspect/Case stages carry the element; Survey
   has none, so this never affects it. */
.stage-loading {
  position: absolute; left: 50%; top: 52%; transform: translate(-50%, -50%);
  display: flex; align-items: center; gap: .6rem;
  color: var(--text-faint);
  font-family: var(--font-mono); font-size: var(--text-sm); letter-spacing: .06em;
  opacity: 1; transition: opacity var(--dur-slow) var(--ease-out);
  pointer-events: none; z-index: 0;
}
.stage:has(.scene-ready) .stage-loading,
.stage.scene-off .stage-loading { opacity: 0; }
.stage-loading-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 10px var(--accent);
  animation: pulse 1.4s var(--ease-in-out) infinite;
}

/* road-condition "reading…" / "unavailable" pending row in the measurement panel */
.readout-pending dd { font-style: italic; color: var(--text-faint); }

/* surface probe — the depth read that follows the cursor over the condition corridor */
.surface-probe {
  position: fixed; z-index: 8; pointer-events: none;
  transform: translate(14px, -50%);
  padding: .3rem .6rem; border-radius: var(--radius-sm);
  font-family: var(--font-mono); font-size: var(--text-xs); font-weight: 600;
  letter-spacing: .04em; white-space: nowrap;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: blur(8px);
  border: 1px solid var(--hairline-data);
  box-shadow: var(--shadow-md);
}
.surface-probe[hidden] { display: none; }
.surface-probe.dip { color: #ff8a8a; border-color: color-mix(in srgb, #ff5d5d 55%, transparent); }
.surface-probe.bump { color: #8ab6ff; border-color: color-mix(in srgb, #5d8bff 55%, transparent); }
.surface-probe.sound { color: #7af0bf; border-color: color-mix(in srgb, #4fd49a 45%, transparent); }

/* ════════════════════════════════════════════════════════════════════════════
   THE RAIL (gallery.js) ⚗️ TRIAL
   Three cards at a time, every one the same size, moved only by the reader. The
   wall this replaces was nine big tiles — a lot of page, and nothing considered
   about it. A rail is quieter: a short row of evidence, a hairline of progress,
   and the whole capture a click away in the inspector.
   ════════════════════════════════════════════════════════════════════════════ */
.band--gallery { max-width: min(1320px, 100%); }
.band--gallery h2 { text-wrap: balance; }
.nowrap { white-space: nowrap; }             /* "mock-up." never breaks at its own hyphen */

.rail { position: relative; }
.rail-track {
  --cards: 3;
  --gap: clamp(14px, 1.4vw, 22px);
  display: flex; gap: var(--gap);
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  /* A card LEAVES the frame rather than being cut off by it. The fade only exists on
     a side there is more rail on (--fl / --fr, set from the scroll position), so the
     first card's caption is never dimmed by a fade with nothing behind it. */
  --fl: 0; --fr: 1;
  -webkit-mask-image: linear-gradient(90deg, rgba(0,0,0,calc(1 - var(--fl))) 0, #000 calc(var(--fl) * 4%),
                      #000 calc(100% - var(--fr) * 4%), rgba(0,0,0,calc(1 - var(--fr))) 100%);
          mask-image: linear-gradient(90deg, rgba(0,0,0,calc(1 - var(--fl))) 0, #000 calc(var(--fl) * 4%),
                      #000 calc(100% - var(--fr) * 4%), rgba(0,0,0,calc(1 - var(--fr))) 100%);
  padding: 4px 2px 6px;
  scrollbar-width: none;
}
.rail-track::-webkit-scrollbar { display: none; }
.rail-track:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: var(--radius-md); }
.rail-track.is-dragging { cursor: grabbing; scroll-snap-type: none; }

.card {
  flex: 0 0 calc((100% - (var(--cards) - 1) * var(--gap)) / var(--cards));
  scroll-snap-align: start;
  display: flex; flex-direction: column; min-width: 0;
  border-radius: var(--radius-md);
  border: 1px solid color-mix(in srgb, var(--hairline-tool) 34%, transparent);
  background: var(--surface);
  overflow: hidden;
  opacity: 0; transform: translateY(10px);
  transition: opacity .45s var(--ease-out), transform .45s var(--ease-out),
              border-color .2s var(--ease-out), box-shadow .25s var(--ease-out);
  transition-delay: calc(var(--i, 0) * 55ms);
}
.card.is-in { opacity: 1; transform: none; }
.card-open {
  position: relative; display: block; width: 100%; margin: 0; padding: 0; border: 0;
  aspect-ratio: 16 / 10; overflow: hidden; cursor: zoom-in;
  background: color-mix(in srgb, var(--text) 4%, var(--bg));
}
.card-open picture, .card-open img { display: block; width: 100%; height: 100%; }
.card-open img { object-fit: cover; transition: transform .6s var(--ease-out); }
.card-open::after {                            /* the gold light that follows the pointer */
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(18rem circle at var(--mx, 50%) var(--my, 50%),
              color-mix(in srgb, var(--accent) 15%, transparent), transparent 62%);
  opacity: 0; transition: opacity .2s var(--ease-out);
}
.card-zoom {
  position: absolute; top: .6rem; right: .6rem; width: 1.9rem; height: 1.9rem; border-radius: 50%;
  background: color-mix(in srgb, var(--bg) 70%, transparent); backdrop-filter: blur(6px);
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  opacity: 0; transform: scale(.9); transition: opacity .2s var(--ease-out), transform .2s var(--ease-out);
}
.card-zoom::before, .card-zoom::after { content: ''; position: absolute; }
.card-zoom::before { left: .47rem; top: .47rem; width: .6rem; height: .6rem; border: 1.5px solid var(--accent); border-radius: 50%; }
.card-zoom::after { left: 1.05rem; top: 1.05rem; width: .36rem; height: 1.5px; background: var(--accent); transform: rotate(45deg); transform-origin: left center; }
@media (hover: hover) and (pointer: fine) {
  .card:hover {
    border-color: color-mix(in srgb, var(--hairline-tool) 85%, transparent);
    box-shadow: var(--shadow-md);
  }
  .card:hover .card-open::after, .card:hover .card-zoom { opacity: 1; transform: none; }
  .card:hover .card-open img { transform: scale(1.02); }
}
.card-open:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* The RICS caveat, carried on the figure that shows the report levels. */
.card-caveat {
  position: absolute; left: .55rem; right: .55rem; bottom: .55rem;
  padding: .3rem .5rem; border-radius: var(--radius-sm);
  font-size: .64rem; line-height: 1.3; text-align: left; color: var(--text-muted);
  background: color-mix(in srgb, var(--bg) 82%, transparent); backdrop-filter: blur(6px);
  border-left: 1px solid color-mix(in srgb, var(--text) 30%, transparent);
}
.card-cap {
  display: grid; gap: .15rem; padding: .7rem .85rem .8rem;
  border-top: 1px solid color-mix(in srgb, var(--hairline-data) 55%, transparent);
}
.fig-no {
  margin: 0; font-family: var(--font-mono); font-size: .64rem; letter-spacing: .18em;
  text-transform: uppercase; color: var(--accent);
}
.card-title {
  font-size: var(--text-sm); font-weight: 600; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* The arrows sit OUTSIDE the cards, in the page's own margin, so nothing covers
   the evidence. They fade out at the ends rather than vanishing. */
.rail-nav {
  position: absolute; top: calc(50% - 1.6rem); z-index: 2;
  width: 2.6rem; height: 2.6rem; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 1.4rem; line-height: 1; color: var(--text); cursor: pointer;
  border: 1px solid color-mix(in srgb, var(--text) 18%, transparent);
  background: color-mix(in srgb, var(--bg) 78%, transparent); backdrop-filter: blur(8px);
  transition: opacity .2s var(--ease-out), border-color .2s var(--ease-out), color .2s var(--ease-out);
}
/* Outside the cards entirely, in the page's margin: an arrow never covers evidence. */
.rail-prev { left: -3.1rem; } .rail-next { right: -3.1rem; }
.rail-nav:hover { border-color: var(--accent); color: var(--accent); }
.rail-nav:disabled { opacity: .25; cursor: default; }
.rail-nav:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.rail-foot {
  display: grid; grid-template-columns: auto minmax(6rem, 16rem) 1fr; align-items: center;
  gap: 1rem; margin-top: var(--space-5);
}
.rail-count { margin: 0; font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-faint); letter-spacing: .1em; }
.rail-bar { height: 2px; background: color-mix(in srgb, var(--text) 12%, transparent); border-radius: 2px; overflow: hidden; }
.rail-bar span {
  display: block; height: 100%; background: var(--accent); transform-origin: left center;
  transition: transform .2s var(--ease-out);
}
.rail-note { margin: 0; text-align: right; font-size: var(--text-xs); color: var(--text-faint); line-height: 1.5; }

@media (max-width: 1100px) {
  .rail-track { --cards: 2; }
  .rail-prev { left: -1rem; } .rail-next { right: -1rem; }
  .rail-foot { grid-template-columns: auto 1fr; }
  .rail-note { grid-column: 1 / -1; text-align: left; }
}
@media (max-width: 640px) {
  .rail-track { --cards: 1.12; }              /* the next card peeks, so the rail reads as one */
  .rail-nav { display: none; }                /* swipe is the gesture here */
}

/* ── THE INSPECTOR — a native modal <dialog> (inert page behind, Esc to close) ── */
html.viewer-open { overflow: hidden; }
.viewer {
  position: fixed; inset: 0; width: 100vw; height: 100dvh; max-width: none; max-height: none;
  margin: 0; padding: 0; border: 0;
  color: var(--text);
  background: var(--bg);                       /* opaque: the wall must not ghost through */
  overscroll-behavior: contain;
}
.viewer[open] { display: grid; grid-template-rows: auto minmax(0, 1fr) auto; }
.viewer::backdrop { background: rgba(0, 0, 0, .55); }
.viewer-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: .8rem 1.25rem; border-bottom: 1px solid var(--divider);
}
.viewer-id { display: grid; gap: .1rem; min-width: 0; }
.viewer-id h2 { margin: 0; font-size: var(--text-base); font-weight: 600; outline: none; }
.viewer-tools { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; justify-content: flex-end; }
.viewer-count { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-faint); margin-right: .5rem; }
.v-btn {
  min-width: 2.2rem; height: 2.2rem; padding: 0 .75rem; border-radius: var(--radius-sm);
  border: 1px solid color-mix(in srgb, var(--text) 18%, transparent);
  background: transparent; color: var(--text); font: 500 var(--text-xs)/1 var(--font-sans);
  cursor: pointer; display: inline-flex; align-items: center; gap: .4rem; justify-content: center;
}
.v-btn:hover { border-color: color-mix(in srgb, var(--accent) 60%, transparent); }
.v-btn[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); }
.v-icon { padding: 0; font-size: 1rem; }
.v-btn:focus-visible, .v-nav:focus-visible, .v-thumb:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.viewer-body { display: grid; grid-template-columns: minmax(0, 1fr) 21rem; min-height: 0; }
.viewer-stage {
  position: relative; overflow: hidden; touch-action: none; cursor: zoom-in;
  background:
    radial-gradient(circle at 50% 40%, color-mix(in srgb, var(--text) 5%, transparent), transparent 70%);
}
.viewer-stage.is-zoomed { cursor: grab; }
.viewer-stage.is-zoomed:active { cursor: grabbing; }
.viewer-canvas { position: absolute; left: 0; top: 0; transform-origin: 0 0; will-change: transform; }
.viewer-canvas.is-gliding { transition: transform .22s cubic-bezier(.23, 1, .32, 1); }
.viewer-img {
  display: block; width: 100%; height: 100%; user-select: none;
  border-radius: calc(6px * var(--inv, 1));
  box-shadow: 0 30px 80px rgba(0, 0, 0, .45);
}
/* Notes: numbered gold pins on the capture, the same size at any zoom (--inv). */
.viewer-pins { position: absolute; inset: 0; margin: 0; padding: 0; list-style: none; pointer-events: none; }
.v-pin {
  position: absolute; width: 0; height: 0;
  opacity: 0; transition: opacity .18s var(--ease-out);
}
.v-pin span {
  position: absolute; left: 0; top: 0;
  display: grid; place-items: center; width: 1.7rem; height: 1.7rem; border-radius: 50%;
  transform: translate(-50%, -50%) scale(var(--inv, 1));
  background: var(--accent); color: var(--on-accent);
  font: 700 .78rem/1 var(--font-mono);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 28%, transparent), 0 6px 18px rgba(0, 0, 0, .45);
}
.notes-on .v-pin { opacity: 1; }
.notes-on .v-pin:nth-child(2) { transition-delay: 40ms; }
.notes-on .v-pin:nth-child(3) { transition-delay: 80ms; }
.notes-on .v-pin:nth-child(4) { transition-delay: 120ms; }

.v-nav {
  position: absolute; top: 50%; width: 2.8rem; height: 2.8rem; margin-top: -1.4rem; border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--text) 20%, transparent);
  background: color-mix(in srgb, var(--bg) 70%, transparent); backdrop-filter: blur(6px);
  color: var(--text); font-size: 1.5rem; line-height: 1; cursor: pointer;
}
.v-prev { left: 1rem; } .v-next { right: 1rem; }
.v-nav:hover { border-color: var(--accent); color: var(--accent); }
.viewer-caveat {
  position: absolute; left: 1rem; bottom: 1rem; margin: 0; max-width: 32ch;
  font-size: .72rem; line-height: 1.4; color: var(--text-muted);
  padding-left: .7rem; border-left: 1px solid color-mix(in srgb, var(--text) 30%, transparent);
}
.viewer-caveat:empty { display: none; }

.viewer-side {
  display: flex; flex-direction: column; gap: 1rem; overflow-y: auto;
  padding: 1.25rem 1.25rem 1.5rem; border-left: 1px solid var(--divider);
}
.viewer-cap { margin: 0; color: var(--text-muted); font-size: var(--text-sm); line-height: 1.6; }
.viewer-notes { list-style: none; margin: 0; padding: 0; display: none; gap: .8rem; }
.notes-on .viewer-notes { display: grid; }
.viewer-notes li { display: grid; grid-template-columns: 1.7rem 1fr; gap: .6rem; font-size: var(--text-sm); color: var(--text); line-height: 1.45; }
.v-pin-no {
  display: grid; place-items: center; width: 1.5rem; height: 1.5rem; border-radius: 50%;
  background: var(--accent); color: var(--on-accent); font: 700 .7rem/1 var(--font-mono);
}
.viewer-side .fig-proves { margin-top: auto; white-space: normal; }

.viewer-strip {
  display: flex; gap: .5rem; overflow-x: auto; padding: .7rem 1.25rem;
  border-top: 1px solid var(--divider); scrollbar-width: thin;
}
.v-thumb {
  position: relative; flex: none; width: 6.5rem; height: 3.9rem; padding: 0; border-radius: var(--radius-sm);
  border: 1px solid color-mix(in srgb, var(--text) 16%, transparent); overflow: hidden; cursor: pointer;
  background: var(--surface); opacity: .6; transition: opacity .18s var(--ease-out), border-color .18s var(--ease-out);
}
.v-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.v-thumb span {
  position: absolute; left: .3rem; bottom: .25rem; font: 600 .62rem/1 var(--font-mono); color: #fff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .8);
}
.v-thumb:hover { opacity: .9; }
.v-thumb[aria-current="true"] { opacity: 1; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }

@media (max-width: 900px) {
  .viewer-body { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr) auto; }
  .viewer-side { border-left: 0; border-top: 1px solid var(--divider); max-height: 30vh; }
  .viewer-count { display: none; }
}

/* The tile grows into the inspector (View Transitions). Ease-out, large-surface curve. */
::view-transition-group(fig-hero) { animation-duration: .38s; animation-timing-function: cubic-bezier(.32, .72, 0, 1); }
::view-transition-old(root), ::view-transition-new(root) { animation-duration: .25s; }

@media (prefers-reduced-motion: reduce) {
  .card { transform: none; transition: opacity .15s linear; transition-delay: 0s; }
  .card-open img, .card-zoom, .card-open::after, .v-pin, .viewer-canvas.is-gliding { transition: none; }
  .card:hover .card-open img { transform: none; }
  .rail-track { scroll-behavior: auto; }
}


/* ── THE MATERIAL LIBRARY ────────────────────────────────────────────────────
   A recreation of the app's own Materials table (MaterialsDataTable.jsx): Name,
   Colour, Density, Value, SKU, in that order. Jeremy, 2026-09-14, on the first
   attempt: "the material list is just plain wrong it needs to be a recreated
   version of this". The point of the panel is that a real library sits behind the
   colours on the model, so it has to look like a library and not like a chart
   legend somebody drew for a slide.

   ⭐ TONNES, NOT KILOGRAMS. The app stores density in kg/m3 and price per kg
   because that is what the arithmetic wants. Nobody in a quarry talks that way.
   1.90 t/m3 and £16.50/t are the same numbers in the language of the yard.

   ⛔ STATIC ON SCREEN, exactly like the grading proof on Inspect. It does NOT
   travel with its card. The copy holds the left through this beat, the library
   holds the right, and the yard sits between them. */
.yard-key {
  position: fixed;
  z-index: 1;
  /* ⭐ HIGH ON THE SCREEN, RIGHT EDGE ON THE "AGAINST WHAT" CARD'S, AND FIXED.
     Jeremy, 2026-09-15: moved up (top at 23% of the screen, read off his mark-up) so
     the lines to the piles have room to breathe, with the .yard-note under it. It does
     not travel with any card. `--yard-key-left` is set by story.js from that card's
     right edge (horizontal only, so it never follows the scroll); the fallback keeps
     it right of centre. The size and shape are signed off: only the position changed. */
  left: var(--yard-key-left, calc(50% + 40px));
  top: 23vh;
  width: min(620px, 62vw);
  box-sizing: border-box;
  /* ⚠️ Tight on purpose. At the first pass this box was 481 px tall, which put its
     top edge at 29% of a 900 px screen and left the yard nowhere to be. Twelve rows
     of five columns is a lot of table; the height has to come out of the leading,
     not out of the rows, because every row is a colour that needs a line drawn to
     it. Measured at 1600x900: the top edge must stay below the halfway line. */
  padding: 14px 16px 10px;
  border-radius: var(--radius-lg);
  /* Nearly opaque. It is a table of numbers over a lit quarry, and a veil that
     works for three lines of copy does not work for five columns of figures. */
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: blur(26px) saturate(1.1);
  border: 1px solid color-mix(in srgb, var(--accent-line) 40%, transparent);
  box-shadow: var(--shadow-lg);
  opacity: 0; visibility: hidden;
  pointer-events: none;
  will-change: opacity;
}
.yard-key h3 {
  margin: 0 0 8px;
  font-family: var(--font-display); font-weight: 700;
  font-size: var(--text-sm); letter-spacing: .14em; text-transform: uppercase;
  color: var(--accent);
}
.yard-key table { width: 100%; border-collapse: collapse; }
.yard-key th {
  text-align: left; font-weight: 600; font-size: 10px;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--text-muted);
  padding: 0 0 5px; border-bottom: 1px solid color-mix(in srgb, var(--accent-line) 45%, transparent);
  white-space: nowrap;
}
.yard-key td {
  padding: 2.5px 0;
  font-size: 11.5px;
  line-height: 1.25; color: var(--text);
  border-bottom: 1px solid color-mix(in srgb, var(--divider) 45%, transparent);
  white-space: nowrap;
}
.yard-key tr:last-child td { border-bottom: 0; }
.yard-key .c-sw    { padding-right: var(--space-3); }
.yard-key .c-name  { width: 100%; padding-right: var(--space-3); white-space: normal; }
.yard-key .c-num   { text-align: right; font-variant-numeric: tabular-nums;
                     padding-left: var(--space-3); }
.yard-key .c-sku   { text-align: right; padding-left: var(--space-3);
                     color: var(--text-muted); font-size: 11px; }
.yard-key .c-cur   { text-align: right; padding-left: var(--space-3);
                     color: var(--text-muted); font-size: 11px; }
/* The swatch is the whole point of the panel, so it is the app's swatch: a bar,
   not a dot. Wide enough to judge a colour against the model. */
.yard-key .sw {
  display: block; width: 30px; height: 10px; border-radius: 2px;
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / .18), 0 0 0 1px rgb(0 0 0 / .45);
}
.yard-key .foot {
  margin-top: 8px; padding-top: 7px;
  border-top: 1px solid color-mix(in srgb, var(--accent-line) 45%, transparent);
  display: flex; justify-content: space-between; align-items: baseline;
  font-size: 11px; color: var(--text-muted);
}
.yard-key .foot b { font-size: var(--text-sm); color: var(--accent);
                    font-variant-numeric: tabular-nums; }

/* THE NOTE UNDER THE TABLE — how a volume becomes tonnes and money, in place.
   Same glass, same width and left edge as the table; story.js seats its top a fixed
   gap below the table's bottom, so the pair holds together at any screen height. */
.yard-note {
  position: fixed;
  z-index: 1;
  left: var(--yard-key-left, calc(50% + 40px));
  top: var(--yard-note-top, 60vh);
  width: min(620px, 62vw);
  box-sizing: border-box;
  padding: 12px 16px 12px;
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: blur(26px) saturate(1.1);
  border: 1px solid color-mix(in srgb, var(--accent-line) 40%, transparent);
  box-shadow: var(--shadow-lg);
  opacity: 0; visibility: hidden;
  pointer-events: none;
  will-change: opacity;
}
.yard-note h3 {
  margin: 0 0 6px;
  font-family: var(--font-display); font-weight: 700;
  font-size: var(--text-sm); letter-spacing: .14em; text-transform: uppercase;
  color: var(--accent);
}
.yard-note p { margin: 0; font-size: 12.5px; line-height: 1.45; color: var(--text-muted); }
.yard-note .sum {
  margin-top: 8px; padding-top: 7px;
  border-top: 1px solid color-mix(in srgb, var(--accent-line) 45%, transparent);
  font-size: 12px; color: var(--text); font-variant-numeric: tabular-nums;
}
.yard-note .sum b { color: var(--accent); font-weight: 600; }

@media (max-width: 1100px) { .yard-key, .yard-note { display: none; } }

/* ── THE REPORT THAT TURNS ITS OWN PAGES (beat 3, "Measure") ──────────────────
   Fixed on screen beside the held Measure card, placed by story.js (placeReport):
   the Inspect ledger's rule, a gap of 5.1% of the window between the two frames, so
   the pair reads as one composition. The canvas (pageCurl.js) overhangs this box so
   the curl has room; the box itself is exactly one sheet. */
.report-curl {
  position: fixed; z-index: 1;
  top: 50%; transform: translateY(-50%);
  left: var(--report-left, 12vw);
  height: 70vh; aspect-ratio: 1415 / 2000;
  opacity: 0; visibility: hidden;
  pointer-events: none;
}
/* The stack's own shadow: every sheet shares this rectangle, so CSS can carry it. */
.report-curl::before {
  content: ""; position: absolute; inset: 0; border-radius: 2px;
  box-shadow: 0 30px 60px rgba(0, 0, 0, .55), 0 6px 16px rgba(0, 0, 0, .35);
}
/* A touch of dark over the model while the report is up: the eye goes to the
   document, the scene stays alive behind it. */
.report-dim {
  position: absolute; inset: 0; pointer-events: none;
  background: #000; opacity: 0;
}
/* THE LIFT's figures: the app's three header tiles (Volume, Mass, Value), in the page's
   glass. Fixed, centred low, clear of the lifted stock; faded and counted by story.js. */
.lift-tiles {
  position: fixed; z-index: 1;
  left: 50%; bottom: clamp(56px, 10vh, 120px); transform: translateX(-50%);
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px;
  width: min(640px, 80vw);
  opacity: 0; visibility: hidden; pointer-events: none;
}
.lift-tile {
  padding: 12px 16px 12px;
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  backdrop-filter: blur(20px) saturate(1.1);
  border: 1px solid color-mix(in srgb, var(--accent-line) 40%, transparent);
}
.lift-tile .k {
  display: block; font-size: 11px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--accent); margin-bottom: 4px;
}
.lift-tile b {
  display: inline; font-family: var(--font-display); font-weight: 700;
  font-size: clamp(1.25rem, 1.7vw, 1.7rem); color: var(--text);
  font-variant-numeric: tabular-nums;
}
.lift-tile em { font-style: normal; margin-left: 6px; font-size: 13px; color: var(--text-muted); }
.lift-cap {
  grid-column: 1 / -1; margin: 2px 0 0; text-align: center;
  font-size: 12px; color: var(--text-muted);
}
/* The two sections, stacked in the right third, clear of the lifted stock. Right edge on
   the site's column (the right-hand cards' edge), set by story.js as --secs-left, so it
   keeps the page's alignment rather than the window's. */
.lift-sections {
  position: fixed; z-index: 1;
  left: var(--secs-left, auto); right: auto; top: 50%; transform: translateY(-50%);
  width: var(--secs-width, 42ch);
  display: flex; flex-direction: column; gap: 8px;
  opacity: 0; visibility: hidden; pointer-events: none;
}
.lift-sec, .lift-note {
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--bg) 80%, transparent);
  backdrop-filter: blur(20px) saturate(1.1);
  border: 1px solid color-mix(in srgb, var(--accent-line) 40%, transparent);
}
.lift-sec { padding: 10px 12px 6px; }
.lift-sec svg { display: block; width: 100%; height: auto; }
.lift-sec .t { font: 700 11px/1 var(--font-sans); letter-spacing: .14em; text-transform: uppercase; fill: var(--accent); }
.lift-sec .ve { font: 10px/1 var(--font-sans); fill: var(--text-muted); }
.lift-sec .ax { font: 9.5px/1 var(--font-sans); fill: var(--text-muted); font-variant-numeric: tabular-nums; }
.lift-sec .grid { stroke: color-mix(in srgb, var(--text) 10%, transparent); stroke-width: 1; }
.lift-sec .surf { fill: none; stroke: var(--text); stroke-width: 1.6; stroke-linejoin: round; }
.lift-sec .gnd { fill: none; stroke: #8fb0ea; stroke-width: 1.6; stroke-linejoin: round; }
.lift-sec .under { fill: color-mix(in srgb, #5c80c4 14%, transparent); }
.lift-sec .key { font: 10px/1 var(--font-sans); fill: var(--text-muted); }
.lift-note { margin: 0; padding: 12px 16px; font-size: 13px; line-height: 1.5; color: var(--text-muted); }
/* At card width the SVG's own 600-wide type would shrink to nothing: its text is sized up
   in viewBox units so it lands near the page's small type once scaled. */
.lift-sec .t { font-size: 17px; }
.lift-sec .ve, .lift-sec .key { font-size: 14px; }
.lift-sec .ax { font-size: 14px; }
@media (max-width: 1100px) { .lift-tiles, .lift-sections { display: none; } }

/* The small box that fades in under the Measure card while it is lifted (story.js,
   paintReport). The card's own look, measured to the card's width in code, so the two
   read as one stacked pair. Not a .beat-card: that class is faded by scroll position,
   and this box is placed and faded by the report's choreography instead. */
.measure-more {
  position: fixed; z-index: 1;
  left: var(--mm-left, 60vw); top: var(--mm-top, 60vh); width: var(--mm-width, 42ch);
  box-sizing: border-box;
  padding: var(--space-5) var(--space-6) var(--space-5) var(--space-5);
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--bg) 58%, transparent);
  backdrop-filter: blur(20px) saturate(1.15);
  border: 1px solid color-mix(in srgb, var(--accent-line) 40%, transparent);
  box-shadow: var(--shadow-lg);
  opacity: 0; visibility: hidden; pointer-events: none;
  will-change: opacity;
}
.measure-more p { margin: 0; color: var(--text-muted); font-size: var(--text-base); }
@media (max-width: 1100px) { .report-curl, .measure-more { display: none; } }

/* ── THE BANNERS — "Not a quarry tool." (banners.js) ──────────────────────────
   Three cloth banners drop from a rail across #b-record, one per sector.

   ⭐ CLOTH IS OPAQUE, AND THAT IS THE POINT. Every other card on this page is
   glass: a veil over the quarry, because the quarry is the argument underneath
   it. These are not. They are the moment we stop showing you the pit and start
   telling you it was never about the pit, so the material is solid, it covers
   what is behind it, and fifteen lines of copy stay readable over lit rock at
   every shot. It also sidesteps stacking backdrop-filter under a filter, which
   is where large blurred panels over a WebGL canvas go wrong.

   ⭐ THE SHADOW IS FREE AND IT IS THE WHOLE TRICK. `filter: drop-shadow` follows
   the CLIPPED silhouette, not the element box — so as the clip line travels down
   the panel, the shadow travels with it and falls on the quarry below the hem.
   That single line is what makes it read as material being released rather than
   a rectangle being wiped in.
   ========================================================================== */
.banners {
  position: fixed; inset: 0; z-index: 1;
  pointer-events: none;
  display: block;
}

/* The statement, alone in the frame before anything drops. */
.banner-say {
  position: absolute; left: 0; right: 0;
  top: 50%;
  margin: 0; padding: 0 var(--space-6);
  text-align: center;
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(2.2rem, 5.2vw, 4.6rem);
  line-height: var(--leading-tight); letter-spacing: -0.02em;
  color: var(--text);
  opacity: 0; visibility: hidden;
  will-change: opacity, transform;
  /* Its own ground, so it reads over bright rock without a card around it —
     the same answer the hero uses, and for the same reason. */
  text-shadow: 0 2px 30px var(--bg), 0 0 90px var(--bg);
}
/* ⛔ A TEXT-SHADOW IS NOT ENOUGH OVER LIT ROCK. Measured in headless: the words sat
   straight over a sunlit bench and the glow washed out against it. Same answer as
   the hero — a soft oval on the copy itself, sized so it never resolves into a
   shape. z-index -1 sits it behind the words and in front of the model. */
.banner-say::before {
  content: "";
  position: absolute; inset: -42vh -18vw;
  pointer-events: none; z-index: -1;
  background: radial-gradient(ellipse 52% 46% at 50% 50%,
    color-mix(in srgb, var(--bg) 92%, transparent) 0%,
    color-mix(in srgb, var(--bg) 66%, transparent) 46%,
    transparent 76%);
}

/* The rail the banners hang from. */
.banner-rail {
  position: absolute; left: 50%; transform: translateX(-50%);
  top: clamp(72px, 12.5vh, 170px);
  width: min(1320px, 92vw);
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: clamp(14px, 1.6vw, 28px);
  /* STRETCH, not start. Content-sized panels of different lengths left a foot of
     dead cloth under the shorter two; stretching squares them off against the
     longest and the quarry stays visible underneath, which is the point — the
     proof is still on screen while we tell you it was never about the pit. */
  align-items: stretch;
}

.banner {
  position: relative;
  --reveal: 0%; --hem: 0%; --hem-a: 0; --tip: 0deg; --ink: 0;
  opacity: 0; visibility: hidden;
  padding: clamp(18px, 2vw, 30px) clamp(16px, 1.7vw, 28px) clamp(22px, 2.4vw, 34px);
  border-radius: var(--radius-md);
  /* The material. A little warmer at the top, as light falls on a hung cloth. */
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--ink-700) 96%, var(--gold-deep)) 0%,
      var(--ink-800) 26%,
      var(--ink-850) 100%);
  border-left: 1px solid color-mix(in srgb, var(--accent-line) 34%, transparent);
  border-right: 1px solid color-mix(in srgb, var(--accent-line) 34%, transparent);
  /* Revealed from the rail down, and the shadow follows the revealed edge. */
  clip-path: inset(0 0 calc(100% - var(--reveal)) 0 round var(--radius-md));
  filter: drop-shadow(0 10px 24px rgba(0, 0, 0, 0.55));
  /* The top edge swings down onto the rail and flattens. */
  transform: perspective(1400px) rotateX(var(--tip));
  transform-origin: top center;
  will-change: clip-path, transform, opacity;
}

/* THE RAIL FITTING — a gold hairline across the top, so the banner reads as hung
   from something rather than floating. Gold: this is the instrument speaking. */
.banner::before {
  content: "";
  position: absolute; left: 0; right: 0; top: 0; height: 2px;
  background: linear-gradient(90deg,
    transparent 0%, var(--gold-deep) 12%, var(--gold-bright) 50%, var(--gold-deep) 88%, transparent 100%);
  opacity: 0.85;
}

/* THE WEIGHTED HEM — rides the reveal line, sits just above it so the clip does
   not eat it, and fades once the cloth has settled. Without this the fall reads
   as a wipe; with it, the panel has a bottom edge with mass. */
.banner::after {
  content: "";
  position: absolute; left: 0; right: 0;
  top: calc(var(--hem) - 3px); height: 3px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--gold) 55%, transparent),
    color-mix(in srgb, var(--gold-deep) 85%, transparent));
  opacity: var(--hem-a);
}

/* The ink, arriving after the cloth. */
.banner h3,
.banner ul { opacity: var(--ink); }

.banner h3 {
  margin: 0 0 clamp(12px, 1.4vw, 20px);
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(1.15rem, 1.5vw, 1.7rem);
  line-height: var(--leading-snug); letter-spacing: -0.01em;
  /* ⛔ NOT GOLD, AND NOT PURE WHITE EITHER. Both were tried on screen.
     Pure white was the brightest thing in the beat and pulled the eye off the
     drawing. Gold put a SECOND gold mass directly under the gold drawing and the
     two competed — Jeremy: "gold text under the gold image over did it."
     ⭐ So gold means exactly one thing in this panel: the instrument's own marks,
     which is the drawing, the ticks and the hairline. Type is greyscale and gets
     its hierarchy from weight and size. A few words slightly off warm-white lead
     the panel without glaring; it is four lines of body copy at full white that
     fatigues, not a heading. */
  color: color-mix(in srgb, var(--text) 86%, var(--text-muted));
  text-align: center;
}
.banner ul { margin: 0; padding: 0; list-style: none; display: grid; gap: clamp(9px, 1vw, 15px); }
.banner li {
  position: relative;
  padding-left: clamp(22px, 1.7vw, 30px);
  color: var(--text-muted);
  font-size: clamp(0.83rem, 0.93vw, 1.02rem);
  line-height: 1.45;
}
/* The tick. Drawn, not a glyph: a font's check mark is a different weight in
   every family and it never sits on the baseline where you want it. */
.banner li::before {
  content: "";
  position: absolute; left: 2px; top: 0.36em;
  width: clamp(9px, 0.62vw, 12px); height: clamp(5px, 0.34vw, 7px);
  border-left: 2px solid var(--accent); border-bottom: 2px solid var(--accent);
  transform: rotate(-45deg);
  opacity: 0.9;
}

/* ── NARROW / NO FILM ────────────────────────────────────────────────────────
   Below 1100 there is no scene and no scroll film, so there is nothing to
   choreograph against. The banners become ordinary content: in the flow, fully
   drawn, stacked, and readable. The words are in the markup precisely so this
   costs nothing. */
@media (max-width: 1100px) {
  /* .beat is a centred flex row, so a block child sizes to its content and
     leaves a gutter down one side. Take the full column. */
  .banners { position: static; flex: 1 1 auto; width: 100%; }
  .banner-say {
    position: static; opacity: 1 !important; visibility: visible !important;
    transform: none !important; margin-bottom: var(--space-6); text-align: left;
  }
  .banner-rail {
    position: static; transform: none; width: auto;
    grid-template-columns: 1fr; gap: var(--space-5);
  }
  .banner {
    opacity: 1 !important; visibility: visible !important;
    clip-path: none !important; transform: none !important;
    filter: none; min-height: 0;
    border: 1px solid color-mix(in srgb, var(--accent-line) 34%, transparent);
  }
  .banner::after { display: none; }
  .banner h3, .banner ul { opacity: 1; }
}

/* Motion is decoration here; the words are the content. */
@media (prefers-reduced-motion: reduce) {
  .banner { transition: none; }
}

/* ── BANNERS · the fuller panel: plate, summary, then the ticks ───────────────
   Jeremy, 2026-09-16: near full height, and each panel reads image → summary →
   bullets. The plate is a DaVinci-style line sketch, gold on black, NOT a photo:
   a stock photograph would be the only untrue thing on a page where everything
   else is a real survey, and it would break the brand in the one beat whose job
   is to widen it. Until the art is drawn the plate holds its exact space so the
   proportion and the scroll can be judged honestly.
   ⛔ Any generated art is Qwen (Apache 2.0). FLUX weights are NON-COMMERCIAL and
   this is a commercial site. See reference_comfyui_image_engine_setup. */
.banner-say em {
  font-style: normal;
  color: var(--accent);
  /* The whole argument turns on this one word, so it gets the gold and nothing
     else does. Underlining or italicising it as well would be shouting twice. */
}

.banner {
  display: flex; flex-direction: column;
  /* ⛔ THE PANEL MUST FIT THE VIEWPORT, AND THE CAP IS WHAT ENFORCES IT — but a cap
     that is shorter than the content silently eats the last tick (measured: the
     fifth Construction line, gone, at 82vh with a 16:9 plate). If you lengthen a
     summary or add a bullet, shoot it and count the ticks. */
  max-height: 84vh;
}

/* THE PLATE — where the sketch goes. */
.banner-plate {
  position: relative;
  flex: 0 0 auto;
  aspect-ratio: 2 / 1;
  margin-bottom: clamp(12px, 1.3vw, 19px);
  border-radius: var(--radius-sm);
  border: 1px solid color-mix(in srgb, var(--accent-line) 45%, transparent);
  background:
    repeating-linear-gradient(-45deg,
      transparent 0 9px,
      color-mix(in srgb, var(--gold-deep) 9%, transparent) 9px 10px),
    var(--ink-900);
  display: grid; place-items: center;
  overflow: hidden;
}
.banner-plate img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* The note is scaffolding: it names the drawing that belongs here, and it goes
   the moment the plate carries a real one. */
.banner-plate-note {
  padding: 0 1.2em; text-align: center;
  font-family: var(--font-mono);
  font-size: clamp(0.62rem, 0.66vw, 0.76rem);
  letter-spacing: var(--tracking-wide);
  color: color-mix(in srgb, var(--accent) 62%, transparent);
}
/* ⛔ ONCE THE DRAWING IS IN, THE PLATE ITSELF DISAPPEARS. Jeremy, 2026-09-16:
   "no frames on the banners, it needs to sit within the banner like it always
   lived there." So the hairline, the hatch and the ground all go, and what is
   left is ink on the panel's own material. The art is a TRANSPARENT PNG (see
   tools/sector_plate.py) precisely so there is no rectangle to colour-match —
   retint the panel or move it and the plate still belongs. The hatched box below
   is scaffolding for the empty state only. */
.banner-plate:has(img) { border: 0; background: none; }
.banner-plate:has(img) .banner-plate-note { display: none; }

.banner-sum {
  margin: 0 0 clamp(14px, 1.5vw, 22px);
  padding-bottom: clamp(13px, 1.4vw, 20px);
  /* ⭐ GOLD HAIRLINE, not the white data one. These panels are the instrument
     talking about what it does, which is the gold side of the page's own
     language (tokens.css: gold = a tool, white = data). */
  border-bottom: 1px solid var(--hairline-tool);
  /* ⛔ NOT --text. Full warm-white body copy on near-black at this length is
     glare, and Jeremy read it as jarring on screen. This sits between --text and
     --text-muted: brighter than the ticks below it, so the hierarchy survives,
     without burning. */
  color: color-mix(in srgb, var(--text) 55%, var(--text-muted));
  text-align: center;
  font-size: clamp(0.86rem, 0.96vw, 1.06rem);
  line-height: 1.5;
  opacity: var(--ink);
}
.banner-plate { opacity: var(--ink); }
/* The list takes the slack so three panels of different lengths square off
   against each other without stretching the plate or the summary. */
.banner ul { flex: 1 1 auto; align-content: start; }

@media (max-width: 1100px) {
  .banner { max-height: none; }
  .banner-plate, .banner-sum { opacity: 1; }
  .banner-plate { aspect-ratio: 16 / 7; }
}

/* ── WHAT IT ADDS UP TO — the last thing in the scroll, before the invitation ──
   ⛔ NO NUMBER GOES IN HERE, EVER. Jeremy, 2026-09-16: "we don't need a number and
   I don't want one." An ROI percentage on a page this careful is the one line a
   buyer could catch us on, and a figure is a claim, which gets evaluated rather
   than felt.
   ⭐ IT WEARS THE PAGE'S OWN CARD. Everything here is position and fade; the look
   comes from .beat-card, unchanged, exactly as every other beat on the page. That
   is deliberate — see the markup for why the bespoke version had to go. */
.beat--banners { min-height: 260vh; }

.banner-close {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  opacity: 0; visibility: hidden;
  will-change: opacity, transform;
}
.banner-close .beat-card { max-width: 46ch; }

@media (max-width: 1100px) {
  .beat--banners { min-height: 0; }
  .banner-close {
    position: static; transform: none;
    opacity: 1 !important; visibility: visible !important;
    margin-top: var(--space-8);
  }
}


/* ── THE MEASUREMENT RAIL IS HIDDEN ──────────────────────────────────────────
   ⛔ HIDDEN, NOT REMOVED, AND DELETE THESE TWO LINES TO BRING IT BACK. Jeremy,
   2026-09-16. The scroll has already shown road grade, distance, volumes, stocks
   and the delta by the time anyone presses Take control, and showing them again
   inside website chrome taught a presentation the app does not have. What the
   hand-over is for now is the one thing nothing else can prove: the model itself,
   up close, at full resolution.
   ⭐ Evidence it was not load-bearing: the rail threw on every click for three
   days (36916a1 to 7fae936) and nobody noticed.
   ⚠️ roadCondition.js and volumeCompare.js STAY — the scroll's own overlays are
   built on them. Only this UI is out. */
/* ⛔ SCOPED TO #explore ON PURPOSE. These classes only live inside the take-control
   panel today, but scoping it means this can never reach the scroll's own overlays
   even if a class name is reused later. The scroll is untouched. */
#explore .explore-rail,
#explore .readout { display: none; }


/* ══ QUESTIONS PAGE ═════════════════════════════════════════════════
   No stage, no model, no viewer: this page is plain content on solid ground and is the
   fastest thing on the site by a distance. That is deliberate — it is the page most
   likely to be ranked and quoted, and speed feeds both. */
.page-questions { background: var(--bg); }
.qa-main { max-width: 960px; margin: 0 auto; padding: calc(var(--space-16) + 40px) var(--space-6) var(--space-12); }

.qa-head { margin-bottom: var(--space-12); }
.qa-head h1 {
  margin: 0 0 var(--space-4);
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(2rem, 4.2vw, 3.2rem); line-height: var(--leading-snug);
  letter-spacing: -0.015em;
}
.qa-head .lede { margin: 0; max-width: 60ch; }
.qa-head .lede a, .qa article a {
  color: var(--accent); text-decoration: none; border-bottom: 1px solid currentColor;
}
.qa-head .lede a:hover, .qa article a:hover { opacity: .8; }

/* The jump list. Real links to real ids, so it is navigation for a reader and a map of
   the page for a crawler. */
.qa-jump { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-6); }
.qa-jump a {
  padding: .45em 1em; border: 1px solid var(--hairline-tool); border-radius: var(--radius-pill);
  background: var(--card-tool-bg); color: var(--text-muted); text-decoration: none;
  font-family: var(--font-mono); font-size: var(--text-xs);
  letter-spacing: .06em; text-transform: uppercase;
}
.qa-jump a:hover { color: var(--accent); border-color: var(--accent); }

.qa { margin-bottom: var(--space-12); scroll-margin-top: 90px; }
.qa > h2 {
  margin: 0 0 var(--space-6); padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--hairline-tool);
  font-family: var(--font-display); font-weight: 700;
  font-size: var(--text-2xl); color: var(--accent);
}
:root[data-theme="light"] .qa > h2 { color: var(--bronze-strong); }
.qa article { margin-bottom: var(--space-6); }
.qa article h3 {
  margin: 0 0 var(--space-2);
  font-family: var(--font-display); font-weight: 700; font-size: var(--text-lg);
}
.qa article p { margin: 0; max-width: 72ch; color: var(--text-muted); line-height: 1.65; }

.qa-foot { text-align: center; padding-top: var(--space-10); border-top: 1px solid var(--divider); }
.qa-foot p { margin: 0 0 var(--space-5); color: var(--text-muted); font-size: var(--text-lg); }

/* The footer nav. ⛔ The footer used to carry no links at all: the one place every
   crawler looks, and a reader who reaches the bottom of a page had nowhere to go. */
.foot-nav { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-5); margin-top: var(--space-4); }
.foot-nav a {
  color: var(--text-muted); text-decoration: none;
  font-size: var(--text-sm); border-bottom: 1px solid transparent;
}
.foot-nav a:hover { color: var(--accent); border-bottom-color: currentColor; }

/* The honeypot. ⛔ NOT display:none and NOT hidden: some bots skip fields they can tell
   are invisible, and some screen readers announce a display:none field anyway. Moved off
   canvas instead, and taken out of the tab order in the markup. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
