/* landing.css — styling for the marketing/orientation "front door" pages
   (index.html, quick-check.html) only. Deliberately self-contained and NOT
   shared with shell.css/workspaces.css — those style the Instrument Panel
   and its nav shell, and pulling that in here is exactly what would make a
   "dramatically simpler" landing experience inherit the main app's visual
   complexity. Colors intentionally match the rest of the site (#0f172a /
   #f1f5f9 / #3b82f6) so the page still feels like CheckMyARM, just with a
   much smaller, calmer set of components. */

/* Design-language tokens — introduced with the Landing Page Design
   Language pass (see CLAUDE.md, "Landing Page Design Language
   (Implementation)"), then carried forward into the Experience Prototype
   rebuild (see CLAUDE.md, "Landing Page Experience Prototype") which
   replaced that pass's own hero and gauge-motif rules with the current
   hero, gauge-analog, clarity, and voice ones (see the "THE EXPERIENCE"
   section below). Not yet retrofitted onto every existing rule in this
   file (a larger, separate migration) — older rules (Quick Check
   results, the entry-card mobile query, etc.) are left as literal values
   to avoid unrelated visual drift. Meant to become the shared basis for
   Quick Check, Results, Reports, and the Instrument Panel as those
   surfaces adopt this language over time, not just this page.
   NOTE FOR FUTURE EDITS: a literal asterisk immediately followed by a
   forward slash inside a CSS comment closes that comment early and has
   silently broken this exact file three times before writing this note
   -- when referring to a class-prefix pattern like "foo-*" alongside
   another one, use "and"/commas, never a bare slash, between them. */
:root {
  --space-xs: 0.5rem;
  --space-sm: 1rem;
  --space-md: 1.75rem;
  --space-lg: 3rem;
  --space-xl: 5rem;
  --text-body: 1.05rem;
  --text-lead: 1.75rem;
  --text-hero: 2.75rem;
  --ink-primary: #f1f5f9;
  --ink-secondary: #cbd5e1;
  --ink-muted: #64748b;
  --accent: #3b82f6;
  --accent-soft: #93c5fd;

  /* RESPONSIVE SCALE — one shared, bounded UI scale expressing "how much
     of the roomy-desktop composition currently fits." Conceptually
     `min(1, availableHeight / preferredHeight, availableWidth /
     preferredWidth)`: 900px tall / 1400px+ wide is the preferred, 1.0
     "roomy desktop" reference this whole page was built against (the
     same 900px reference the rest of this file's own vh-based sizing
     already assumes, and the same 1180px "no longer roomy desktop"
     floor the dock/stack breakpoints already use); the scale eases
     linearly down from there toward a 0.75 floor as either dimension
     shrinks, reached at 550px height / 960px width -- comfortably
     before the real non-desktop fallbacks (the <1180px dock breakpoint,
     the <768px stacked layout) take over, so this scale is doing real
     work across the whole "still desktop, just shorter" range those
     fallbacks don't cover. `min()` of the two component scales means
     whichever dimension is tighter wins.
     Wrapped around a substantially wider set of rules than the prior
     pass -- not just a few font-sizes, but the dimensions that actually
     determine the settled composition's footprint: hero/Mission
     typography, the compact teleprompter's own height (so it compresses
     FASTER than plain vh alone, closing the "large empty gap around a
     small text block" this scale exists to fix), action-card padding/
     type/button, the gaps between hero message, action cards, footnote,
     terminology, and the footer, terminology-card sizing, education-card
     sizing, header/gauge sizing, footer padding, and Quick Check's own
     surface padding, headings, checklist, and result-view spacing.
     Still a plain multiplier on values already in use, not a second
     responsive "mode" -- nothing branches on it, no layout structure
     changes. */
  --ui-scale-h: clamp(0.75, calc(0.75 + (100vh - 550px) * 0.25 / 350px), 1);
  --ui-scale-w: clamp(0.75, calc(0.75 + (100vw - 960px) * 0.25 / 220px), 1);
  --ui-scale: min(var(--ui-scale-h), var(--ui-scale-w));

  /* A SECOND, steeper curve for exactly one box: the compact
     teleprompter (Mission's own window). Verified live: even at 1.0
     scale, that box's own min-height leaves it roughly 2x taller than
     the text it flex-centers actually needs -- by far the single
     largest "unused vertical space" contributor on the page, and the
     main reason the gentler --ui-scale alone couldn't close the
     footer/action-card overlap at constrained heights. Still reaches
     exactly 1.0 at the same 900px reference (so roomy desktop is
     unaffected), but drops much faster below it -- always paired with
     an explicit min() floor at its own two call sites (never used
     bare) so it can compress hard without ever risking clipping real
     Mission text, which this file's own overflow:hidden window would
     otherwise clip permanently with no way to scroll to see it. */
  --ui-scale-deep: clamp(0.22, calc(0.22 + (100vh - 780px) * 0.78 / 120px), 1);
}

* {
  box-sizing: border-box;
}

body.landing-body {
  margin: 0;
  font-family: system-ui, -apple-system, sans-serif;
  background: #0f172a;
  color: #f1f5f9;
}

/* line-height: 1.6 moved off body.landing-body itself and onto its own
   content children (excluding #app-shell) -- a unitless line-height set
   on body inherits into EVERY descendant, recomputed against that
   descendant's own font-size, which reached nav.js's shared header
   markup (#app-shell > .shell-nav, index.html only) with no rule in
   shell.css/header-shared.css to override it -- .shell-logo's line box
   measured 35.84px there vs. Reading Room's un-inherited 29.6px for the
   identical element/class/font-size, a real, confirmed rendered-geometry
   difference (see CLAUDE.md, "Header Normalization" diagnosis). The
   shared header should inherit no landing-specific typography, so this
   is narrowed onto the landing page's own content -- every direct body
   child except the header -- rather than compensated for with a reset
   inside .shell-nav, which would leave the leak itself unaddressed.
   quick-check.html also carries body.landing-body but has no #app-shell
   element at all (it renders its own separate, un-shared header
   directly as a body child) -- :not(#app-shell) is therefore a no-op
   there, and that page's line-height (including inside its own header)
   is completely unchanged by this. */
body.landing-body > *:not(#app-shell) {
  line-height: 1.6;
}

/* ───────── Header ─────────
   Three-column grid, not a plain flex row -- "the center context is
   independently centered on the viewport, NOT in the remaining flex
   space" (CLAUDE.md, "Header Normalization — visual contract
   correction"). The two OUTER tracks share the identical 1fr weight, so
   they're always equal-width regardless of how much content is actually
   in the LEFT cluster vs. the RIGHT cluster -- which is what pins the
   CENTER (auto-sized) track's own midpoint to the row's true midpoint,
   not to whatever's left over after two differently-sized siblings.
   Exactly mirrors shell.css's own .shell-desktop-row -- same technique,
   same reasoning, applied to this page's separate implementation. Box
   model (padding/font-family) now reads the shared --hdr-py/--hdr-px/
   --hdr-font-family tokens (header-shared.css) rather than its own
   literal values -- byte-identical to .shell-nav's own. Border-bottom
   moved onto .experience-header itself (below), which now also owns
   this element's background -- consolidating what were two separately-
   declared rules for the same two classes on the same <header>. */
.landing-header {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  padding: var(--hdr-py) var(--hdr-px);
  font-family: var(--hdr-font-family);
}

.experience-header-left {
  grid-column: 1;
  justify-self: start;
  display: flex;
  align-items: center;
}

/* CSS-only fallback for the true no-JS case (syncHeaderOffset() in
   index.html never runs at all without JS) -- a conservative estimate
   of the real header height (gauge height + vertical padding + border),
   verified against the live-measured value rather than guessed blind.
   JS-enabled visitors immediately get the real, precise measurement
   instead; this is only ever what a no-JS visitor actually sees. */
#experience-header-spacer {
  height: 88px;
}

.landing-logo-link {
  text-decoration: none;
  display: flex;
  align-items: center;
  font-size: 1.4rem;
  font-weight: 800;
  color: #3b82f6;
  letter-spacing: -0.5px;
}

.landing-logo-link img {
  height: 26px;
  width: auto;
  margin-right: 0.5rem;
}

.landing-logo-link span.word {
  color: #f1f5f9;
}

.landing-logo-link span.tm {
  font-size: 0.5em;
  font-weight: 400;
  vertical-align: super;
  color: #64748b;
  margin-left: 0.1em;
}

/* The instrument -- reuses the exact same .gauge-analog-arc/
   -needle-group/-needle/-hub classes and @keyframes gauge-search
   defined further down in this file (the "analog gauge" section), just
   at header scale. margin-left: auto pushes it to the far edge of the
   existing flex row without touching the logo's own layout. Bad/Good
   labels and the caption are dropped at this size -- illegible that
   small, and the header isn't where that explanation belongs anyway.
   Sized substantially larger than its prior 40px -- direct instruction
   that "it currently exists, but is so small and visually quiet that it
   effectively disappears... not enormous, just unmistakably part of the
   product identity." 76px is roughly 1.9x the previous size: large
   enough to register at a glance without a second look, comfortably
   short of dominating the identity bar the way the logo itself should
   still be the primary anchor there. The SVG's own viewBox scales
   cleanly at this size -- no path/stroke changes were needed. */
/* ───────── Contextual header shortcuts ─────────
   Compact, muted text-buttons -- "subordinate to the CheckMyARM identity
   beside it, not a conventional site nav bar." Visibility/labels are
   entirely state-driven (index.html: wireHeaderShortcuts()/the SKIP-
   toggle inside wireScrollFade(); quick-check.html: its own separate,
   simpler Menu-only header) -- nothing here hides anything via a fixed
   rule, so no CSS-only no-JS fallback is needed beyond the [hidden]
   attribute already doing its job. .header-shortcuts (the plural
   container class this section used to also define, for index.html's
   own now-removed inline Skip/Start/Continue row) was removed this pass
   -- nav.js renders that row's equivalent directly inside
   .shell-desktop-right/.shell-mobile-panel now (see nav.js's
   extraActionsHtml()), with no page-specific wrapper class of its own
   left to style. */

/* Real bug, found and fixed: SKIP intro stopped disappearing once the
   settled workspace was reached. wireScrollFade()'s own hide logic
   (index.html) was never broken -- it correctly sets el.hidden = true on
   every [data-extra-action="skip"] element the instant progress crosses
   NARRATIVE.mission, confirmed live. What silently defeated it: nav.js's
   extraActionsHtml() renders Skip/Start/Continue as .hdr-btn.hdr-btn-
   secondary (header-shared.css) -- never .header-shortcut, a leftover
   class from before that centralization that no longer matches anything
   real (the dead [hidden] rule this comment replaces). .hdr-btn's own
   `display: inline-flex` has the exact same specificity as the browser's
   native `[hidden] { display: none }` UA rule -- and author styles
   always outrank UA styles at equal specificity, so .hdr-btn silently
   won regardless of source order, the identical failure mode this
   codebase has hit before on .shell-dropdown-panel. Scoped to
   [data-extra-action="skip"] specifically (not header-shared.css's own
   .hdr-btn, and not every [data-extra-action]) per explicit instruction
   to fix this as a landing-specific Skip-visibility correction, not a
   shared nav/header architecture change -- Start/Continue are never
   programmatically hidden, so they never needed this override and never
   will. */
.header-shortcut {
}

[data-extra-action="skip"][hidden] {
  display: none;
}

/* The whole-site Menu trigger (renamed from "Learn more" -- see
   CLAUDE.md, "Normalize the CheckMyARM shared header architecture") --
   same .header-shortcut pill, plus the shared icon/label pairing
   (header-shared.css) every other CMA header's Menu control now uses. */
/* .header-shortcut-menu declares nothing of its own now -- .hdr-btn
   already supplies display/align-items/gap identically; kept only as an
   identification hook. */
.header-shortcut-menu {
}

/* .hdr-menu-panel (header-shared.css, applied directly in markup)
   supplies the shared background/border/radius/shadow/padding -- the
   SAME chrome shell.css's own .shell-menu-panel now uses -- so this
   selector keeps only what's genuinely specific to this page's OWN
   panel: its absolute position relative to the trigger, min-width, and
   the flex-column item layout. A prior version had its OWN independent
   background/border/shadow (rgba(15,23,42,0.98)/#2a3750/a differently-
   sized shadow) that never matched shell.css's panel at all -- confirmed
   the actual, visible root cause of "Menu trigger and Menu panel still
   differ between landing and shared-shell pages." */
.header-learn-menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 220px;
  display: flex;
  flex-direction: column;
  z-index: 60;
}

.header-learn-menu[hidden] {
  display: none;
}

/* .hdr-menu-panel-item (header-shared.css, applied directly in markup)
   supplies display/padding/radius/color/hover -- identical to the plain
   <a> links here and to every item inside shell.css's own Menu panel.
   This selector's own prior font-size/hover values (differing slightly
   from .hdr-menu-panel-item's) are removed outright rather than left to
   conflict with it. */
.header-learn-menu a {
}

/* ───────── New-PC settling advisory ─────────
   Now a direct header-level sibling positioned right after the meter
   (see the HTML's own comment) rather than a member of the nav
   shortcuts row -- "the advisory's desktop position must not shift when
   Skip intro appears/disappears," satisfied structurally, since nothing
   about its own position depends on anything to its right.

   No pill/button chrome at all -- "read as an informational advisory,
   not another nav button." Left-aligned, understated inline text
   instead: an underlined amber-tinted line (this codebase's existing
   "notice, not danger" accent -- see .clarity-card-accent-amber) with
   no background/border/radius. flex: 0 1 auto (never grows) + min-width:
   0 + a max-width cap means it takes only its own natural text width up
   to that cap, never stretching into the flexible gap the task's own
   layout diagram reserves for the space BETWEEN the advisory and the
   nav (that gap is margin-left: auto on .header-menu-wrap, below) --
   this element is sized to itself, not to fill available room.
   white-space: nowrap + text-overflow: ellipsis (never wrapping to a
   second line) is the one hard constraint every rule here protects:
   the header must never grow taller, so if there is ever genuinely not
   enough room for the full label, it truncates on one line rather than
   wrapping -- the full text is always still reachable via the panel
   this trigger opens regardless of how much of the label is currently
   visible. */
/* .experience-header-center's own layout/centering rule (grid-column: 2,
   justify-self: center, the true-viewport-centering mechanism) and its
   900px hide-breakpoint both moved to header-shared.css this pass --
   this class was a byte-for-byte duplicate of that file's own version
   (originally written here before header-shared.css absorbed it once
   nav.js started rendering this same cell for shell pages too), which
   never conflicted in OUTCOME but was exactly the kind of "two
   independently-tuned copies of the same rule" this file's own header
   comment already warns against. The advisory itself still centers
   correctly with no change here -- it's a CHILD of that shared cell, not
   this rule. See CLAUDE.md, "Header Normalization — one real shared
   implementation." */

/* Much narrower now that the content itself is two short lines rather
   than one long one -- a real, substantial reduction in horizontal
   footprint (see CLAUDE.md's own before/after measurements), which is
   the actual mechanism behind "leave more room for contextual actions
   on the right, especially at intermediate laptop/desktop widths." */
.header-advisory-wrap {
  position: relative;
  max-width: min(260px, 46vw);
}

/* Two explicit lines (line1/line2 spans, below), not wrapped text --
   "prefer this compact two-line treatment BEFORE hiding the callout
   because of horizontal space." Fixed at exactly two lines by design:
   no white-space:normal/wrapping here, the break point is the space
   between the two <span>s in the markup, deliberate and stable at every
   width down to CENTER_HIDE_BREAKPOINT, never a third accidental line.
   Does NOT grow the header taller -- "there is already sufficient
   vertical room": two lines at this font-size/line-height total well
   under the gauge's own ~65px content height, which is what actually
   determines the header's height (see .header-gauge, below), so the
   center column's own content simply has room to spare underneath it. */
.header-shortcut-advisory {
  appearance: none;
  -webkit-appearance: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.15rem;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  text-align: center;
  font: inherit;
  cursor: pointer;
}

.header-advisory-line1 {
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: #c9a05f;
  text-decoration: underline;
  text-decoration-color: rgba(217, 164, 65, 0.35);
  text-underline-offset: 2px;
  white-space: nowrap;
  transition: color 0.15s ease, text-decoration-color 0.15s ease;
}

/* Stronger CTA emphasis than line 1, per the brief's own suggestion --
   brighter color, heavier weight, slightly larger. */
.header-advisory-line2 {
  font-size: 0.8rem;
  font-weight: 800;
  letter-spacing: 0.02em;
  color: #e8bc6a;
  text-decoration: underline;
  text-decoration-color: rgba(217, 164, 65, 0.5);
  text-underline-offset: 2px;
  white-space: nowrap;
  transition: color 0.15s ease, text-decoration-color 0.15s ease;
}

.header-shortcut-advisory:hover .header-advisory-line1,
.header-shortcut-advisory:focus-visible .header-advisory-line1 {
  color: #dfc08a;
  text-decoration-color: rgba(240, 205, 133, 0.65);
}

.header-shortcut-advisory:hover .header-advisory-line2,
.header-shortcut-advisory:focus-visible .header-advisory-line2 {
  color: #f0cd85;
  text-decoration-color: rgba(240, 205, 133, 0.85);
}

.header-shortcut-advisory:focus-visible {
  outline: 2px solid var(--accent-soft, #93c5fd);
  outline-offset: 3px;
  border-radius: 3px;
}

/* .header-advisory-panel/.header-advisory-sources (the hover-triggered
   info panel this advisory used to open, with generic advice plus two
   external source links) are REMOVED -- the advisory is a plain <a>
   linking directly to the Reading Room's own, much fuller article on
   this exact topic now (see index.html's ADVISORY_HTML), which needs
   no separate explanatory panel of its own. */

/* ───────── Compact header menu (hamburger) ─────────
   .header-menu-wrap is now the header's own grid-column: 3 -- flush at
   the far-right edge because it's the last, right-justified grid cell
   (see .landing-header's grid-template-columns, below), never because
   of a margin pushing it there. Its own #header-menu-trigger (the OLD,
   separate, icon-only hamburger that used to appear ONLY below
   HEADER_MENU_BREAKPOINT and swap the ENTIRE nav#header-shortcuts row
   into its own dropdown panel) is REMOVED outright this pass, along
   with its .header-menu-icon glyph and the [data-menu-open] attribute
   mechanism that drove it -- see CLAUDE.md, "Header Normalization —
   visual contract correction": "the right-most control should REMAIN
   the permanent whole-site Menu... never itself replaced by a separate,
   generic hamburger." Still real, still used by quick-check.html's own
   separate header (its Menu trigger+panel, styled via .hdr-btn/.hdr-
   btn-secondary + .hdr-menu-icon/.hdr-menu-label + .hdr-menu-panel,
   header-shared.css, applied directly alongside this class in that
   page's markup). index.html no longer renders an element with this
   class at all -- its own equivalent is nav.js's .shell-menu-wrap /
   .shell-menu-trigger (shell.css) now. This section's own former
   duplicate-Skip/Start/Continue-inside-the-Menu-panel mechanism
   (.header-menu-panel-shortcuts, a SHORTCUTS_FOLD_BREAKPOINT media
   query, and .hdr-menu-panel-divider) was removed outright this pass --
   quick-check.html's Menu panel never had Skip/Start/Continue items to
   begin with (it has no cinematic sequence to short-circuit), and
   index.html's own equivalent duplicates are nav.js's own
   [data-extra-action] mobile-panel copies now (see nav.js's
   extraActionsHtml()), which need no page-specific CSS here. */
.header-menu-wrap {
  grid-column: 3;
  justify-self: end;
  display: flex;
  align-items: center;
  flex-shrink: 0;
  position: relative;
}

/* SKIP/START's shared "brief, non-theatrical emphasis" -- filter/
   box-shadow-glow only, deliberately never `transform`, since several
   pulse targets (the docked education cards, positioned via inline
   style every scroll frame -- see eduCardRect(), index.html) already
   have their OWN transform driven by JS; animating `transform` here too
   would silently fight that system for the animation's duration. A
   layered drop-shadow glow reads as "elevation" without touching a
   property anything else on this page depends on. */
@keyframes shortcut-pulse-anim {
  0%, 100% { filter: brightness(1) drop-shadow(0 0 0 rgba(59, 130, 246, 0)); }
  35% { filter: brightness(1.2) drop-shadow(0 0 18px rgba(59, 130, 246, 0.55)); }
}

.shortcut-pulse {
  animation: shortcut-pulse-anim 0.7s ease;
}

/* "Temporary emphasis must not depend on large motion" -- collapses to
   a near-instant flash rather than skipping it outright, so a
   reduced-motion visitor clicking START still gets SOME acknowledgment. */
body.reduced-motion .shortcut-pulse {
  animation-duration: 0.01s;
}

/* Sits directly beside the logo (see the HTML) -- a small fixed left
   margin for breathing room rather than an auto-margin pushing it to
   the header's far edge (that job belongs to the RIGHT grid track/
   .header-menu-wrap now, not the gauge). Bumped 76px -> 92px --
   "modestly more legible if useful" -- since it no longer needs to
   leave maximum room for a shortcuts row it used to visually compete
   with for space. */
/* SCOPED TO .qc-page (quick-check.html's own body class) -- this file's
   .header-gauge/.header-gauge-wrap/.header-gauge svg used to be
   unscoped, matching by class alone, which meant they ALSO applied to
   nav.js's shared header gauge on index.html (#app-shell > .shell-nav
   renders the identical class names, different ids) -- since this file
   loads after shell.css/header-shared.css there, its width: calc(92px *
   var(--ui-scale)) silently overrode shell.css's own fixed width: 92px
   for the shared gauge, producing a real, viewport-dependent rendered-
   geometry difference from Reading Room's (confirmed via
   getBoundingClientRect() -- see CLAUDE.md, "Header Normalization"
   diagnosis). index.html must rely entirely on shell.css/header-
   shared.css for that gauge's geometry now; these rules are scoped so
   they can never again match anything inside .shell-nav on any page.
   quick-check.html is a genuinely separate, standalone header
   implementation (not nav.js's shared one) and still needs its own
   gauge sized -- .qc-page scoping preserves this exact styling,
   unchanged, for that one page only.

   Wraps #header-gauge + #gauge-explain-panel as SIBLINGS (see the HTML)
   -- deliberately NOT a parent/child nesting, even though that would be
   the more obvious way to anchor the panel "to the gauge." .header-gauge
   itself gets dimmed (opacity/filter) via .header-gauge.muted when there
   is no real reading yet (see quick-check.html's setGauge()) -- CSS
   opacity on an ancestor unavoidably composites its WHOLE subtree at
   reduced alpha, so nesting the panel inside the dimmed element would
   silently dim the tooltip's own text too, directly contradicting
   "remains fully opaque/readable even when the gauge itself is dim."
   Keeping them siblings under one positioned wrapper sidesteps that
   entirely -- the panel's own opacity is never touched by the gauge's
   muted state. */
.qc-page .header-gauge-wrap {
  position: relative;
  display: inline-flex;
  align-items: center;
  margin-left: 0.9rem;
  flex-shrink: 0;
}

.qc-page .header-gauge {
  width: calc(92px * var(--ui-scale));
  flex-shrink: 0;
  cursor: help;
}

.qc-page .header-gauge svg {
  width: 100%;
  height: auto;
  display: block;
}

/* ───────── Layout ───────── */

.landing-main {
  max-width: 780px;
  margin: 0 auto;
  padding: 0 1.25rem 4rem;
}

/* ───────── The Briefing: hero ─────────
   "The Question" stands alone -- no supporting paragraph, no eyebrow. The
   needle motif is the only other element on screen, at rest, pointed at
   nothing yet. This section never participates in .reveal (see below):
   the very first thing a visitor sees must be instantly there, not
   animated in. */

/* ═════════════════════════════════════════════════════════════════
   THE EXPERIENCE — Landing Page Experience Prototype. Replaces the
   prior "Briefing" structure entirely (see CLAUDE.md). The page tells
   one story -- noise, then signal -- through the experience itself, not
   through paragraphs: dozens of independent "voice" fragments breathing
   in the background, a hero that stays anchored while calm educational
   cards scroll past it, and an analog gauge that deliberately never
   settles. Bold and experimental by explicit request; if a future pass
   walks it back, that's an accepted, planned-for outcome, not a failure
   of this one.
   ═════════════════════════════════════════════════════════════════ */

.experience-body {
  overflow-x: hidden;
}

/* Persistent identity bar, used only by quick-check.html now -- index.html
   no longer renders an element with this class at all (its own shared
   header is nav.js's .shell-nav, styled by shell.css directly, not a
   second implementation reading these same tokens -- see CLAUDE.md,
   "Header Normalization — one real shared implementation"). "The header
   should not scroll away during the opening sequence... the CheckMyARM
   identity remains quietly present throughout" -- position: fixed, not
   sticky-within-a-wrapper, since fixed is the only positioning that
   stays anchored to the true viewport regardless of whatever sticky/
   pinning logic the page's own cinematic sequence is doing. z-index: 50
   sits above every other layer (the desk/props stages, the opening-
   sequence stage) so the bar is never occluded by them.

   Smoked-glass surface -- IDENTICAL tokens to shell.css's .shell-nav
   (header-shared.css's --hdr-glass-bg/--hdr-blur/--hdr-border-color),
   never this page's own, separately-tuned literal values -- a prior,
   near-opaque gradient here (rgba(...,0.99) fading to rgba(...,0.94))
   was the actual, confirmed root cause of "the landing/workspace header
   currently reads substantially more opaque than the Reading Room
   header" (see CLAUDE.md, "Header Normalization — visual contract
   correction"). This flat, genuinely translucent fill is what makes
   "no special landing-header visual transition should be necessary
   merely because the background changes behind it" true: #desk-stage/
   .workspace-stage (below) are full-viewport BACKGROUND layers, not
   geometrically confined below this header -- the cinematic desk and
   the settled clean-desk workspace both show through this recipe the
   same amount, the same way Reading Room's own photographed environment
   already does. #props-stage stays confined below the header, on
   purpose -- see its own comment, index.html's markup -- since a
   FOREGROUND cinematic prop reads as clipped/misplaced when it sits
   behind the header's own translucency, unlike passive background
   texture; "environment bleeds behind the glass header, foreground
   props do not." */
.experience-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 50;
  background: var(--hdr-glass-bg);
  -webkit-backdrop-filter: var(--hdr-blur);
  backdrop-filter: var(--hdr-blur);
  border-bottom: 1px solid var(--hdr-border-color);
}

/* ───────── The workspace ─────────
   The finalized Assessment/workspace backdrop (public/assets/landing/
   desk-clean.png) -- a clean, ordered desk, replacing the messy/noisy
   cinematic desk once the visitor arrives at the settled entry-card
   state. Sits BEHIND .desk-stage (z-index -1 vs. 0, below) rather than
   being inserted/removed at the moment it's needed -- it's already in
   place for the entire cinematic sequence, simply hidden by the fully-
   opaque desk photo stacked on top of it, so the only thing that
   changes at handoff is opacity (see wireScrollFade(), index.html),
   never layout or paint order.

   Deliberately unaltered visually -- no scrim, tint, blur, or gradient
   of any kind layered over the photo, unlike .desk-stage's own scrim
   below. The image's own design already has a large quiet center with
   decorative objects confined to the corners specifically so the UI
   (the entry cards' own existing background/border treatment) can
   supply its own contrast, matching the explicit brief for this asset;
   center/center (the default) is therefore the correct position with no
   tuning needed, unlike desk.webp's own off-center composition, which
   needed a specific offset to align its quiet area with the story text.
   `top: 0`/`bottom: 0` (a full-viewport stage, not confined below the
   header the way this layer used to be -- see CLAUDE.md, "Header
   Normalization — one real shared implementation," Requirement 3) --
   "in the settled state, the clean desk/workspace should exist behind
   the [now genuinely translucent] shared header." The header no longer
   needs this layer to stay clear of it; its own smoked-glass recipe
   (var(--hdr-glass-bg)/var(--hdr-blur), shared with .shell-nav) is what
   reveals it. #props-stage (below), a FOREGROUND layer rather than
   background environment, was later reverted back to a below-the-
   header stage -- see its own comment for why "environment bleeds
   behind the glass header, foreground props do not" is now the
   governing rule, not "every fixed layer on this page behaves
   identically." `cover` guarantees no letterboxing
   regardless of viewport aspect ratio, and keeps the image's own
   centered quiet area aligned with the centered content column (the
   hero/entry-card area) at any reasonable desktop width. */
.workspace-stage {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: -1;
  pointer-events: none;
  background: url('/assets/landing/desk-clean.png') center center / cover no-repeat;
  background-color: #0f172a;
  opacity: 0;
  will-change: opacity;
}

/* ───────── The desk ─────────
   The master research-desk photograph (public/assets/landing/desk.webp),
   staged as the dominant, PHYSICALLY REAL environment for Acts 1-3b --
   replacing the prior pass's entirely procedural (CSS-generated)
   noise/environment layers. `background` here layers a dark gradient
   scrim directly ahead of the photo (one property, no separate DOM node
   needed) so the huge centered story text (see .scene-act1, below) stays
   legible against real photographic texture and contrast.

   The scrim itself was cut substantially this pass -- the desk had been
   darkened/muted enough that props (rendered at their own natural
   brightness) read as visibly brighter and more saturated than the
   surface they were supposedly sitting on. Legibility now leans more on
   .scene-line's own text-shadow (a tight, local dark halo directly
   behind the glyphs -- already doing real work, see that rule further
   down) than on a broad scrim darkening the whole scene; this scrim is
   now "just enough," not the primary legibility mechanism.

   `background-position` shifted from `center 38%` toward the image's
   own upper-left -- the master photo's natural quieter gap in the
   clutter sits better behind the centered narrative there, judged
   compositionally (see the live verification notes in CLAUDE.md), not
   derived mathematically. `top: 0`/`bottom: 0` -- a full-viewport stage,
   not confined below the header the way it used to be (see CLAUDE.md,
   "Header Normalization — one real shared implementation," Requirement
   3: "during the cinematic intro, the messy desk/environment should
   exist behind the header"). The header's own translucency is what now
   keeps it legible against whatever's directly behind it -- the same
   smoked-glass recipe .shell-nav uses everywhere else on the site, not
   a geometry exclusion specific to this page.

   `cover` guarantees the photo always fills the stage with no
   letterboxing regardless of aspect ratio. Recedes -- fades, softens,
   pulls back slightly via a small scale-up (see wireScrollFade()'s
   desk-recede computation, index.html) -- only across Act 3b's own
   window, giving "...not yours." real visual space rather than an
   instant cut; what's underneath once it clears is simply this page's
   own plain #0f172a background, already CheckMyARM's calmer world, so
   no separate "arrival" background layer is needed. */
.desk-stage {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 0;
  pointer-events: none;
  background:
    linear-gradient(180deg, rgba(15, 23, 42, 0.3) 0%, rgba(15, 23, 42, 0.12) 32%, rgba(15, 23, 42, 0.1) 55%, rgba(15, 23, 42, 0.36) 100%),
    url('/assets/landing/desk.webp') 36% 16% / cover no-repeat;
  will-change: opacity, filter, transform;
}

/* A somewhat darker scrim on narrow viewports -- cover-fit cropping
   tighter at these widths can leave less of the photo's own natural
   darker area behind the text, and the text itself has less room to
   spread out, so a bit more contrast help is still warranted here --
   reduced alongside the desktop scrim, not left at its old strength. */
@media (max-width: 767px) {
  .desk-stage {
    background:
      linear-gradient(180deg, rgba(15, 23, 42, 0.4) 0%, rgba(15, 23, 42, 0.22) 32%, rgba(15, 23, 42, 0.2) 55%, rgba(15, 23, 42, 0.46) 100%),
      url('/assets/landing/desk.webp') 36% 16% / cover no-repeat;
  }
}

/* ───────── The props ─────────
   Ten real photographed evidence pieces (public/assets/landing/props/),
   choreographed entirely by wireScrollFade()/propRect() in index.html --
   this file only ever supplies their base sizing/decoration; every
   position, rotation, width, opacity, and filter value is written as
   inline style every scroll frame, the identical pattern the removed
   procedural voice field used before it. Sits above the desk (z-index 1
   vs 0) but -- like every fixed-position layer on this page -- still
   paints BELOW .experience-main (z-index 10, see further down), so the
   huge story text and the entry-path cards always paint above every
   prop regardless of where a "held" prop happens to sit.

   `top: var(--stage-header-h, 88px)` -- NOT 0, unlike #desk-stage/
   .workspace-stage just above. "Header Normalization" (see CLAUDE.md)
   originally moved this to top:0 alongside the environment layers, on
   the theory that a prop sitting behind the header's own translucency
   was the same kind of reveal as the desk photo's -- real use found
   that reads wrong for a FOREGROUND cinematic object (a prop the
   visitor is meant to notice and read, like "3 days without a
   charge!") in a way it doesn't for passive background texture: props
   intruding beneath the header looked like clipped/misplaced content,
   not an intentional layering choice. The corrected rule, now load-
   bearing: environment bleeds behind the glass header, foreground
   props do not -- confined back to the same below-the-header stage
   #desk-stage/.workspace-stage themselves used before that pass.
   propRect()'s percentage-based positions (index.html) are relative to
   THIS stage's own height/top, unchanged in every other respect -- a
   smaller stage restores every prop's previously-approved on-screen
   composition automatically, with no change to any prop's own
   rest/held/discard percentages, rotation, timing, or scroll
   choreography (index.html itself is untouched by this correction). */
.props-stage {
  position: fixed;
  top: var(--stage-header-h, 88px);
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1;
  overflow: hidden;
  pointer-events: none;
}

.prop {
  position: absolute;
  max-width: 460px;
  height: auto;
  pointer-events: none;
  will-change: left, top, width, transform, opacity, filter;
}

/* ───────── Three responsive tiers, not two ─────────
   Large (~1180px+): the full cinematic experience -- pinned hero,
   flying props, docked education rails.
   Medium (~768-1179px): still the full cinematic experience (pinned
   hero, flying props) MINUS the docked side rails, which would either
   overlap the centered hero column or need to cram against it -- the
   exhibit rails simply fall back to normal, in-flow document scroll
   below the hero/actions instead (see .story-arena/.exhibit-rail,
   further down, gated at the SEPARATE 1180px breakpoint).
   Small (<768px): simplified aggressively -- no pinned hero, no flying
   props, no docked rails of any kind -- a single top-to-bottom normal-
   flow document (hero -> actions -> terminology -> education cards).
   This breakpoint used to sit at 900px; moved down to 768px this pass
   specifically to grow the medium tier ("do not compromise the
   large-screen experience for small screens" -- the cinematic staging
   now survives down to a genuine tablet/small-laptop width, and only
   true small/handheld viewports get the reduced experience). */
@media (max-width: 767px) {
  .props-stage {
    display: none;
  }
}

/* Reduced-motion visitors already land directly on the calm Mission
   beat with none of the earlier acts ever shown (see .tp-line's own
   reduced-motion handling, further down) -- showing a busy, motionless
   desk scene behind text that skips straight to "we've arrived" would
   contradict itself. Both layers are hidden outright, landing these
   visitors on the plain, already-calm page background directly -- if
   anything a MORE appropriate rest state for someone who's asked for
   reduced motion, not merely a fallback. */
body.reduced-motion .desk-stage,
body.reduced-motion .props-stage {
  display: none;
}

/* .workspace-stage's own opacity is only ever set by wireScrollFade()
   (index.html), which -- like the two rules above -- never runs at all
   for reduced-motion visitors. Without this override they would land on
   the plain flat page background instead of the settled workspace
   backdrop, since nothing would ever move this layer off its default
   opacity:0. They've already arrived at the calm Mission state by
   definition, so the calm workspace backdrop belongs there from the
   first frame, not eased in. */
body.reduced-motion .workspace-stage {
  opacity: 1;
}


/* ───────── Layout: a centered stage, with or without exhibits beside it ─────────
   The hero pins CENTERED on its own; .story-wrapper exists purely to
   give it scroll room to stay pinned through Acts 1-5 (sized by
   wireStorySlack() in index.html). Below 1180px, the exhibit rails
   (see .exhibit-rail further down) are plain stacked content reached
   only once that slack is exhausted and the hero has settled -- no
   column beside it at these widths, matching this file's own earlier
   "they are part of the hero, not a left/right content layout"
   reasoning. At 1180px+ (see the min-width block below), that
   reasoning is deliberately overridden by more specific, later
   direction: the rails become genuine flanking columns, sharing the
   same scroll region as the still-pinned hero -- "exhibits surrounding
   the presentation," not a page section. Either way, the rails don't
   exist anywhere near the viewport during Acts 1-2 -- via document
   distance below 1180px, via padding-top above it (see .exhibit-rail)
   -- never merely hidden with opacity. */

.experience-main {
  position: relative;
  z-index: 10;
}

.story-wrapper {
  position: relative;
  max-width: 1120px;
  margin: 0 auto;
  padding: 0 1.5rem;
}

/* ARCHITECTURAL SIMPLIFICATION — see CLAUDE.md. `.hero-stage` now wraps
   ONLY the cinematic teleprompter (Acts 1-3b) -- the final Mission
   message and the two action cards moved OUT to #landing-decision-card,
   a plain, ordinary, normal-flow sibling below (see the HTML). This is
   what finally lets `.hero-stage` stay `position: sticky` for its
   entire life with no exceptions, no `.compact` state, and no JS-driven
   position-mode switching of any kind: once the intro's own scroll
   room (#story-wrapper's slack, sized in wireStorySlack() against
   NARRATIVE.mission now, not the education cards' later arrival) is
   exhausted, the browser's own native sticky release lets it scroll
   away like any other element, and the decision card -- already
   sitting in normal flow immediately after it -- simply becomes
   reachable. No Y-position math, no release-vs-footer coordination,
   nothing left to get wrong.

   `top` is anchored to the header's own real measured height
   (--stage-header-h, set by nav.js's syncHeaderHeight() now -- see
   CLAUDE.md, "Header Normalization") plus a small breathing gap -- the
   sticky box's own top edge can never start behind the fixed header, at
   any viewport height. Deliberately UNCHANGED by the environmental-
   layers-behind-the-header fix (see #desk-stage/#props-stage/
   .workspace-stage, above): the STORY TEXT itself must always clear the
   header, never sit behind it -- only the background photo layers
   extend underneath it. */
.hero-stage {
  position: sticky;
  top: calc(var(--stage-header-h, 88px) + clamp(0.25rem, 2vh, 1rem));
  max-width: 620px;
  margin: 0 auto;
  padding: clamp(1rem, 6vh, 3rem) 0;
}

/* ───────── No more "clearing" ─────────
   A prior pass carved a soft, feathered patch of blur/darkness out of
   the background directly behind the hero, on the theory that the hero
   needed a quiet patch to be legible from. Reversed on direct
   instruction: "I'd rather keep the room noisy everywhere... let the
   hero stand out because of scale, typography, weight, contrast, and
   perhaps a subtle shadow, not because we've erased the room behind
   it." Legibility comes entirely from .scene-line's own weight/size and
   text-shadow, below. `position:relative;z-index:1` is what lets both
   this teleprompter AND the separate #landing-decision-card (below)
   paint above the fixed desk/prop background layers -- kept as two
   small, independent rules rather than one shared selector now that
   they're no longer DOM siblings under the same parent. */
.teleprompter {
  position: relative;
  z-index: 1;
}

/* ───────── Exhibits ─────────
   Act 6, split across two rails now instead of one section below the
   hero -- "exhibits surrounding the presentation," per direct
   instruction, not a page section reached by scrolling past it. Below
   1180px (see the min-width block further down for the flagship
   desktop treatment) .story-arena stays a plain block and both rails
   just stack in normal document flow, one after another, beneath the
   hero -- honest fallback for a real, previously-verified laptop/
   tablet range too narrow to comfortably fit two flanking rails
   without cramping the center column (checked live at 1000px/1024px/
   1152px: a 1180px cutoff keeps every one of those widths on this
   simpler, safe, already-working layout rather than risking overflow
   at the point where minmax() floors on all three grid columns can no
   longer be satisfied simultaneously). */
/* A fixed, symmetrical two-column grid below the dock breakpoint --
   "once we reach ordinary page content, the presentation should feel
   orderly," in deliberate contrast to the cinematic portion's own
   chaos. repeat(2, 1fr) (not auto-fit) gives exactly two equal-width
   columns rather than however many 260px slots happen to fit;
   align-items: start top-aligns without forcing equal card heights;
   a single `gap` value keeps horizontal/vertical spacing identical.
   max-width unchanged (900px) -- already a sensible constraint, cards
   don't stretch across the full viewport. Harmless above 1180px: the
   dock media query below removes each card from normal flow entirely
   (position: fixed), so this container's own layout mode has no
   visible effect there. Below 768px (this file's existing small-tier
   breakpoint, reused rather than inventing a new one), collapses to a
   single column -- "genuinely too narrow for two useful columns." */
.exhibit-rail {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  align-items: start;
  gap: 1.75rem;
  max-width: 900px;
  margin: 0 auto;
  padding: 1rem 1.5rem 4rem;
}

@media (max-width: 767px) {
  .exhibit-rail {
    grid-template-columns: 1fr;
  }
}

.exhibit-rail-right {
  padding-top: 0;
}

/* ───────── The education dock (flagship desktop) ─────────
   1180px+ only, the same deliberately-narrower-than-"desktop" audience
   as before -- direct instruction to optimize the desktop presentation
   first without compromising it for a phone, and this treatment
   genuinely needs real side margins to avoid cramping the 620px-wide
   center column (see the note above .exhibit-rail).

   Replaces a prior pass's 3-column CSS Grid (rails as normal-flow grid
   siblings, scrolling past a pinned hero) with something structurally
   simpler: every card in either rail becomes position: fixed directly
   -- no grid, no `order`, no padding-top/margin-top offset table. Once
   a card is fixed, it's removed from normal flow entirely. `top` is set
   ONCE per card by wireEduDockPositions() (index.html, a layout
   decision, not a motion effect -- runs unconditionally, including for
   reduced-motion visitors) from EDU_CARDS_LEFT/RIGHT's own per-slot
   percentages; only `opacity`/`transform: translateX(...)` change after
   that, driven every scroll frame by wireScrollFade() the same way
   every other layer on this page animates. Below this breakpoint,
   .clarity-card stays a normal-flow flex child (see .exhibit-rail,
   above) and the pre-existing IntersectionObserver `.reveal` mechanism
   still reveals it on scroll, unchanged.

   `.story-arena`/`.story-wrapper` need no `order`/grid overrides here
   anymore either -- with the rails taken out of flow, .story-wrapper
   is simply the arena's only real flow child at every width, its own
   existing (non-media-query) centering and max-width already correct
   without an override.

   ROOT-CAUSE FIX -- ".exhibit-rail-left/-right need no layout rules
   here... they collapse to zero-height wrappers, harmlessly" was
   never actually true at this breakpoint: the RAIL elements
   themselves still carry the base .exhibit-rail padding (1rem 1.5rem
   4rem) even though their card CHILDREN are position:fixed and
   contribute nothing to that padding's own box -- padding on a block
   element applies regardless of whether its children are in normal
   flow. Confirmed live: #exhibit-rail-left/-right measured 80px/64px
   of real height apiece (144px combined) at this breakpoint, purely
   from that unused padding, silently adding to the page's total
   scrollable length after the wrapper -- exactly the kind of
   "unnecessary post-settlement scroll range" a real end-of-story fix
   needs to remove. Zeroed out HERE, scoped to the dock-active
   breakpoint only -- the base .exhibit-rail padding (above) is
   untouched and still applies below 1180px, where cards genuinely are
   normal-flow content that needs it. */
@media (min-width: 1180px) {
  #exhibit-rail-left .clarity-card,
  #exhibit-rail-right .clarity-card {
    position: fixed;
    width: 232px;
    margin: 0;
  }

  #exhibit-rail-left .clarity-card {
    left: 1.75rem;
  }

  #exhibit-rail-right .clarity-card {
    right: 1.75rem;
  }

  .exhibit-rail-left,
  .exhibit-rail-right {
    padding: 0;
  }
}

/* ───────── The opening sequence: the viewport IS the stage ─────────
   Architectural correction from the prior pass's small "teleprompter"
   window: "the hero should no longer scroll inside a small masked
   region... each thought should occupy almost an entire viewport...
   the viewport itself is the stage, not a scrolling window inside the
   stage." Same underlying mechanism as before (wireScrollFade() sets
   ONE continuous transform on #teleprompter-track, computed from real
   measured line-centers, never opacity), scaled up to full-screen: the
   window is now sized against the real viewport height (--vh-avail, a
   custom property set once by index.html's setViewportUnit() to
   window.innerHeight, since 100vh on mobile browsers includes the
   address bar and would misjudge available height) minus the fixed
   header's own real offset, rather than a small fixed pixel box.
   82vh -- not literally 100vh, "almost an entire viewport" as written,
   deliberately leaving room to breathe below the header and above
   wherever the next thing (the entry paths, once Mission settles)
   begins. Every .tp-line is still unconditionally opacity: 1 always --
   nothing about scaling this up touches that. Deliberately NO
   transition on #teleprompter-track's own transform, unchanged from
   before -- the motion must be a pure, frame-by-frame function of
   scroll position. */
.teleprompter {
  position: relative;
  height: calc(var(--vh-avail, 1vh) * 82);
  overflow: hidden;
  margin-bottom: 1.5rem;
  transition: height 0.4s ease;
}

.teleprompter-track {
  position: relative;
}

/* Every .tp-line fills nearly the entire stage and centers its own
   content -- "the current thought owns the screen." Opacity is never
   set to anything other than 1, anywhere, at any scroll position; what
   makes a line "hidden" is purely physical, the track having translated
   it outside the window's own clipping bounds. */
.tp-line {
  min-height: calc(var(--vh-avail, 1vh) * 80);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  /* Reduce padding first, before typography has to shrink -- fluid
     against height (3vh) with sane rem floor/ceiling. */
  padding: clamp(0.5rem, 3vh, 1.5rem);
  opacity: 1;
}

/* Progressive-enhancement fallback -- without JS running (or with
   prefers-reduced-motion, which deliberately never gets .js-ready, see
   the init sequence in index.html), #teleprompter-track's own transform
   is never set by anything, so this window would otherwise show
   whatever sits at the untranslated TOP of the track (Recognition,
   partially cut off). Since Mission's real content now lives in the
   always-visible-by-default #landing-decision-card (below), ALL four
   tp-lines can simply stay hidden without JS -- nothing important is
   lost, the decision card already shows it. .js-ready restores them
   for the real, physically-scrolling experience. measureLineCenters()
   (index.html) always runs AFTER body.js-ready is added, so it never
   measures a display:none element. */
.tp-line {
  display: none;
}

/* display: flex here, matching .tp-line's own base rule exactly -- a
   real bug caught before shipping: this selector's specificity (body +
   2 classes) beats .tp-line's own (1 class), so an earlier draft that
   left this at "display: block" was silently discarding the flex
   centering the instant .js-ready was added, which is exactly when the
   animated sequence (and therefore this rule) is live. Verified live
   after the fix: computed display reads "flex" once .js-ready is
   present, "none"/"block" (mission only) without it. */
body.js-ready .tp-line {
  display: flex;
}

/* text-shadow carries the legibility job the removed .hero-clearing
   used to (see the note above .teleprompter/.hero-entry further up) --
   a dark, soft glow behind the text itself rather than a
   carved-out quiet patch in the room behind it. Same dark navy as the
   page's own background (was also .hero-clearing's fill color), so it
   reads as the text sitting slightly forward of the scene rather than a
   mismatched halo. */
.scene-line {
  /* Mission's own size (Acts 1-3 override via .scene-act1, below) --
     fluid against BOTH width and height now (min() of a vw term and a
     vh term, whichever is smaller wins), so a wide-but-short viewport
     shrinks this correctly instead of staying oversized just because
     there's horizontal room. Also wrapped in var(--ui-scale) -- the
     clamp()'s own vh/vw terms only meaningfully compress below their
     own ~700px-ish crossover point (see .scene-act1's own comment);
     --ui-scale fills the gap above that, on the shared 900->600px
     scale, instead of this rule needing its own separate second
     threshold. */
  font-size: calc(clamp(1.05rem, min(3.2vw, 4.5vh), 1.5rem) * var(--ui-scale));
  font-weight: 700;
  line-height: 1.35;
  color: var(--ink-primary);
  letter-spacing: -0.01em;
  margin: 0 0 clamp(0.3rem, 1.5vh, 0.7rem);
  text-shadow: 0 2px 16px rgba(15, 23, 42, 0.95), 0 1px 3px rgba(15, 23, 42, 0.9);
}

.scene-line-emphasis {
  color: var(--accent-soft);
}

/* Acts 1-3 -- "these are not headings on a webpage... for the first
   several viewport heights, THESE WORDS ARE THE MOVIE." Substantially
   larger and bolder than Mission's own size (.scene-line, above).
   min(6vw, 8vh) -- fluid against BOTH width and height, not vw alone:
   a wide-but-short viewport (the actual failure mode a pure-vw formula
   has) now correctly shrinks this too, since whichever term is smaller
   wins. Deliberately does NOT shrink for the line that used to read as
   "explanatory" ("...is based on someone else's needs...") -- every
   Act 1-3 line, including that one, shares this exact rule, per direct
   instruction that it is "another major beat," not subordinate to the
   others. */
.scene-act1 {
  /* Ceiling raised 3.6rem -> 4.2rem -- "the hero narration is not
     currently authoritative enough against the desk/prop scenery."
     Same clamp() shape (still fluid against both width AND height via
     min(6vw, 8vh), still --ui-scale-aware), just a higher, more
     prominent resting size at roomy desktop -- a real point at the
     8vh term now crosses back below the ceiling too (~880px viewport
     height, up from ~720px before), so the fluid taper genuinely
     covers a wider range instead of sitting flat at the ceiling
     through most of the "roomy" band. var(--ui-scale) is unchanged --
     at roomy heights (900px+) it stays 1.0, so this is a real, not
     merely theoretical, size increase there. */
  font-size: calc(clamp(1.7rem, min(6vw, 8vh), 4.2rem) * var(--ui-scale));
  font-weight: 800;
  line-height: 1.12;
  letter-spacing: -0.02em;
}

/* Colored emphasis spans within an Act 1-3 line -- the reference
   image's own device of coloring the one or two words that carry the
   line's emotional charge, rather than the whole sentence shouting at
   once. */
.scene-emph-red {
  color: #f87171;
}

.scene-emph-purple {
  color: #c4b5fd;
}

/* Act 4/Mission -- deliberately calmer, not louder. "By this point:
   the noise has receded, the colors have calmed... this is arrival."
   Kept at its own smaller, steady size (unchanged from before this
   pass) so the CONTRAST with Acts 1-3's new scale is what signals
   arrival, not just the copy. */
.scene-mission-line {
  font-size: 1.35rem;
}

/* .scene-mission-lead ("It can be pretty compelling.") was removed --
   the hero copy pass folded its job into .scene-sub's own opening
   clause instead, dropping a full line of vertical height. See
   index.html's own comment above the decision card's copy. */
.scene-sub {
  font-size: 0.98rem;
  font-weight: 400;
  line-height: 1.6;
  color: var(--ink-secondary);
  margin: 0 auto 0.6rem;
  max-width: 480px;
  text-shadow: 0 1px 10px rgba(15, 23, 42, 0.9);
}

.scene-sub strong {
  color: var(--ink-primary);
  font-weight: 700;
}

/* ───────── Terminology row (below the action cards) ─────────
   Moved out of .tp-mission and below .hero-entry this pass -- "reads as
   reference material near where a visitor is about to act, not
   vocabulary homework before the choice they came here for." Restyled
   as "compact cousins of the education cards rather than CTA buttons":
   the old pill/pale-blue-button treatment (.scene-chip) is gone
   entirely, replaced by the same dark-surface-plus-left-accent-border
   language .clarity-card already establishes, just smaller and in a
   single row. One consistent accent (the page's own --accent-soft)
   rather than six different colors -- there's no natural per-term
   category the way education topics have one, and six arbitrary colors
   here would read as decoration, not information. */
.terms-row {
  max-width: 560px;
  margin: calc(1.1rem * var(--ui-scale-deep)) auto 0;
  text-align: center;
}

.terms-row-label {
  font-size: calc(0.7rem * var(--ui-scale));
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--ink-muted);
  margin: 0 0 calc(0.5rem * var(--ui-scale-deep));
}

.term-cards {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.45rem;
}

.term-card {
  position: relative;
  background: #161d2c;
  border: 1px solid #212d42;
  border-left: 3px solid var(--accent-soft, #93c5fd);
  border-radius: 8px;
  padding: calc(0.38rem * var(--ui-scale)) calc(0.7rem * var(--ui-scale));
  font-size: calc(0.76rem * var(--ui-scale));
  font-weight: 600;
  color: var(--ink-primary);
  letter-spacing: 0.01em;
  cursor: pointer;
  user-select: none;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.term-card:hover,
.term-card:focus-visible,
.term-card.disclosure-open {
  background: #1c2740;
  border-color: rgba(255, 255, 255, 0.35);
}

.term-card:focus-visible {
  outline: 2px solid var(--accent-soft, #93c5fd);
  outline-offset: 2px;
}

/* ───────── Terminology definition popovers ─────────
   Smaller and terser than an education card's own expansion panel --
   "conceptual hierarchy: terminology card -> concise definition,
   education card -> mini explainer, assessment -> personalized answer."
   Nested INSIDE its own card (same reasoning as .edu-panel), still
   opening upward (bottom: 100%) -- unlike an education card, a
   terminology card sits at the very bottom of the pinned hero
   composition once settled, so opening upward keeps it comfortably
   clear of the page content immediately below on every viewport height
   checked live. Positioning stays pure CSS. */
.term-panel {
  position: absolute;
  bottom: calc(100% + 9px);
  left: 50%;
  width: 230px;
  max-width: calc(100vw - 3rem);
  transform: translateX(-50%) translateY(4px);
  background: rgba(15, 23, 42, 0.98);
  border: 1px solid rgba(147, 197, 253, 0.35);
  border-radius: 10px;
  padding: 0.7rem 0.9rem;
  box-shadow: 0 14px 32px rgba(0, 0, 0, 0.5);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.15s ease, transform 0.15s ease, visibility 0s linear 0.15s;
  pointer-events: none;
  z-index: 9;
  text-align: left;
  white-space: normal;
  font-weight: 400;
  letter-spacing: normal;
  text-shadow: none;
}

.term-card.disclosure-open .term-panel {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
  pointer-events: auto;
  transition: opacity 0.15s ease, transform 0.15s ease;
}

.term-panel p {
  margin: 0;
  font-size: 0.78rem;
  line-height: 1.55;
  color: var(--ink-secondary, #cbd5e1);
}

.term-panel p strong {
  color: var(--ink-primary, #f1f5f9);
}

.term-panel-close {
  display: none;
  position: absolute;
  top: 4px;
  right: 6px;
  width: 22px;
  height: 22px;
  background: rgba(148, 163, 184, 0.14);
  border: none;
  border-radius: 999px;
  color: var(--ink-secondary, #cbd5e1);
  font-size: 0.9rem;
  line-height: 1;
  cursor: pointer;
}

@media (hover: none) {
  .term-panel-close {
    display: block;
  }
}

body.reduced-motion .term-panel {
  transition: none !important;
}

@media (max-width: 560px) {
  .term-panel {
    width: 200px;
  }
}

/* .scene-cta-line ("Let's find out.") was removed -- the two action
   cards immediately below now ARE the call to action, with no separate
   hinge line needed in front of them. See index.html's own comment
   above the decision card's copy. */

/* ───────── The decision card ─────────
   Ordinary, normal-flow document content -- see the HTML comment above
   #landing-decision-card and CLAUDE.md's Architectural Simplification
   note. Its own Y position is never set, moved, or recalculated by any
   JS; only its APPEARANCE (opacity + a small rise) is animated, toggled
   by the exact same NARRATIVE.entry progress threshold that used to
   drive #hero-entry's own reveal directly. A neutral, subtle surface --
   similar in spirit to .qc-surface (Quick Check's own center-stage
   card) but visually lighter, since this page's action cards (below)
   already carry the real visual weight. */
.landing-decision-card {
  position: relative;
  z-index: 1;
  max-width: 620px;
  margin: 0 auto;
  padding: clamp(1.5rem, 4vh, 2.5rem) clamp(1rem, 4vw, 1.75rem);
  text-align: center;
  background: rgba(17, 26, 46, 0.5);
  border: 1px solid rgba(51, 65, 85, 0.6);
  border-radius: 16px;
}

.js-ready .landing-decision-card {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.js-ready .landing-decision-card.visible {
  opacity: 1;
  transform: translateY(0);
}

/* Each action card still eases in individually -- own opacity/rise, own
   elevation -- riding on top of the outer card's own fade, so the
   composition settles as "container arrives, then its two cards follow
   a beat apart" rather than one flat block popping in. ".entry-detailed"
   gets a longer transition-delay than ".entry-quick" ("action cards
   follow with a small stagger") -- written as fully separate
   `transition` declarations (not a bare `transition-delay` override)
   specifically so the delay applies to opacity/transform only, never to
   the box-shadow elevation lift, which stays on its own identical,
   non-staggered timing for both cards. Gated on the ANCESTOR card's own
   `.visible` class, not a class of their own -- one reveal signal, not
   two independently-toggled ones. */
.js-ready .landing-entry-card {
  opacity: 0;
  transform: translateY(18px);
  box-shadow: 0 0 0 rgba(0, 0, 0, 0);
  transition: opacity 0.5s ease 0.04s, transform 0.5s ease 0.04s, box-shadow 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}

.js-ready .landing-entry-card.entry-detailed {
  transition: opacity 0.5s ease 0.16s, transform 0.5s ease 0.16s, box-shadow 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}

.js-ready .landing-decision-card.visible .landing-entry-card {
  opacity: 1;
  transform: translateY(0);
  /* A genuinely larger, more diffuse lift (bigger offset+blur on both
     layers) than .clarity-card's own resting shadow (0 3px 6px/0 14px
     28px) so the two action cards read as sitting noticeably higher
     above the page than the surrounding education cards, without any
     color/glow -- still just a shadow, still identical on both
     .entry-quick and .entry-detailed. */
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.45), 0 4px 14px rgba(0, 0, 0, 0.35);
}

/* ───────── Whole-card activation ─────────
   The ENTIRE card is now a real navigation target (wireEntryCardActivation(),
   index.html), not just the CTA button at the bottom -- role="link" +
   tabindex="0" + a data-href, matching this file's existing "div as an
   accessible custom trigger" pattern (see .clarity-card's own
   role="button" cards) rather than nesting an <a> around content that
   already contains a real, separate <a class="landing-entry-btn"> --
   nesting an anchor inside an anchor is invalid HTML, which is exactly
   what a naive "wrap the whole card in <a>" approach would produce.
   cursor:pointer is unconditional (works with or without JS/.js-ready);
   the hover/focus treatment itself is also unconditional so it's never
   gated behind the entrance animation happening to have finished. No
   transform is used here at all, specifically to avoid fighting the
   entrance animation's own `transform: translateY(...)`, which the two
   rules above already own. */
.landing-entry-card {
  cursor: pointer;
}

/* ROOT-CAUSE CORRECTION -- real hardware testing (not this environment's
   own synthetic checks, which can't reliably reproduce :hover at all)
   found Detailed Assessment's hover STILL looked visibly bigger/
   splashier than Quick Check's, despite the box-shadow glow rule
   further down already having been made accent-color-free in an
   earlier pass. Traced by reading every rule that touches
   .landing-entry-card on hover/focus, not assumed equivalent just
   because one shared selector already looked fixed: THIS rule was the
   real remaining source. It changed the card's own BACKGROUND to a
   gradient tinted with rgba(var(--entry-accent-rgb), 0.16) on hover --
   a second, independent place accent color was still feeding a hover-
   only visual change, left behind when only the box-shadow glow layer
   was neutralized before. Green and blue read as different intensities
   to the eye even at numerically identical alpha against this page's
   dark navy (the same color-perception effect already documented for
   the box-shadow layer) -- a real, structural asymmetry, not a
   rendering artifact. Fixed by removing --entry-accent-rgb from this
   rule entirely (a flat, colorless slate tint instead) and dropping the
   redundant border-color change (the border is already var(--entry-
   accent) at rest -- restating the same value on hover changed
   nothing, just added one more accent-reading line to re-check next
   time). Accent identity now lives ONLY in the base, always-on border
   (2px solid var(--entry-accent), a separate rule, untouched) and the
   button's own fill (.landing-entry-btn, untouched) -- this hover rule
   no longer reads --entry-accent-rgb or --entry-accent anywhere. */
.landing-entry-card:hover,
.landing-entry-card:focus-visible {
  background: linear-gradient(165deg, rgba(226, 232, 240, 0.1), rgba(15, 23, 42, 0.55));
}

.landing-entry-card:focus-visible {
  outline: 2px solid var(--entry-accent);
  outline-offset: 3px;
}

/* The SAME correction applied here too -- the 1.5px "accent ring" this
   rule used to draw with var(--entry-accent) was the other half of the
   real asymmetry: even a thin ring reads as a different size/intensity
   in blue vs. green at the same width/alpha. "Use a neutral white/
   silver/slate glow rather than green vs. blue accent-colored glow" is
   now true of the WHOLE box-shadow, ring included -- nothing in this
   rule reads --entry-accent/--entry-accent-rgb anymore, so the two
   cards' hover states are now bit-for-bit identical CSS, not merely
   numerically-equal-but-differently-colored. */
.js-ready .landing-decision-card.visible .landing-entry-card:hover,
.js-ready .landing-decision-card.visible .landing-entry-card:focus-visible {
  box-shadow:
    0 0 0 1.5px rgba(226, 232, 240, 0.55),
    0 6px 20px rgba(226, 232, 240, 0.22),
    0 22px 48px rgba(0, 0, 0, 0.5),
    0 6px 18px rgba(0, 0, 0, 0.4);
}

/* A small, continuous brightness lift on the teleprompter window (86%
   -> 100%), tied to the identical scroll-progress value that drives the
   fog/noise fade -- so "the hero becomes more prominent" is something
   the visitor's own scrolling causes, not something the page does on a
   clock. Deliberately modest: this is a supporting cue layered on top
   of the physical scroll itself, not the main mechanism -- and it's a
   continuous filter, not a toggle, so it doesn't reintroduce any
   opacity-based state. */
.teleprompter {
  filter: brightness(calc(0.86 + var(--hero-clarity, 1) * 0.14));
}

body.reduced-motion .hero-stage,
body.reduced-motion .hero-stage * {
  transition: none !important;
}

/* ───────── The analog gauge ─────────
   Explicitly NOT the Instrument Panel's own gauge -- an old analog
   voltmeter instead: a bare 60-degree arc, no numbers. Lives in the
   header (see .header-gauge, near the top of this file), not the
   hero's own center -- a brand element, not a one-off hero animation
   ("this has the potential to become a recognizable visual language
   across the product"). Bad/Good labels and the caption were dropped
   along with the move (illegible at header scale, and the explanation
   belongs elsewhere anyway).

   GOVERNING RULE -- "no evidence = no verdict" (see CLAUDE.md, "ARM-fit
   gauge: implement a genuine unknown state"). Every gauge on this site
   starts DORMANT (see .gauge-dormant, below) -- needle hidden, arc/
   labels muted -- and stays that way in plain static HTML, no JS
   required. index.html's gauge is dormant PERMANENTLY: that page never
   runs an assessment, so it never has a suitability value to show.
   quick-check.html is the one page that can WAKE its gauge, and only
   ever does so once window.AssessmentModel.computeAssessmentResult()
   has actually produced a real tier (see header-gauge.js's activate(),
   called from quick-check.html's renderResults()) -- never earlier, and
   never for the mere fact that a scan is downloading or running.

   Once awake, the needle settles to the real tier's angle, with
   amplitude driven by confidence (steadier at high confidence, never
   fully still even then -- "should never become perfectly still...
   just gently breathe"). Implemented as ONE continuous keyframe
   animation (never a hard switch between an "uncertain" and a "settled"
   animation, which would read as a visible cut) whose stops are
   expressed relative to two CSS custom properties instead of fixed
   degrees: --needle-center (the angle the motion is currently centered
   on) and --needle-amp (how wide the swings around that center
   currently are, 1 = full original search range, near 0 = a small
   breathing wobble). header-gauge.js's activate() sets both once, the
   moment real evidence exists -- there is no more scroll-driven or
   time-based settling toward them; a prior pass on index.html DID tie
   this to scroll progress, which turned out to be exactly the
   conceptual bug this whole section now guards against (a virgin
   visitor with zero evidence was shown a needle that had settled toward
   a mild positive lean by the time they reached the decision card) --
   removed outright rather than adjusted, see index.html's own comment
   above its gauge markup. */

.gauge-analog-arc {
  fill: none;
  stroke: url(#gaugeGradient);
  stroke-width: 3;
  stroke-linecap: round;
  opacity: 0.85;
  transition: opacity 0.6s ease, filter 0.6s ease;
}

/* "Not enormous, doesn't compete with the hero" -- small, muted-but-
   legible labels rather than the reference image's own bold all-caps
   treatment. Colors are deliberately dim (not the arc's own saturated
   red/green) so they read as instrument labeling, not another loud
   accent competing with the identity bar around them. */
.gauge-analog-label {
  font-family: system-ui, -apple-system, sans-serif;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  transition: opacity 0.6s ease;
}

.gauge-analog-label-bad {
  fill: #b45959;
  text-anchor: middle;
}

.gauge-analog-label-good {
  fill: #4f9d6e;
  text-anchor: middle;
}

/* ROOT-CAUSE FIX -- these three selectors also match the SHARED gauge
   nav.js renders on every shell room (index.html included, since the
   Header Normalization pass), which reuses the same .gauge-analog-*
   class names on its own, completely different photographic-dial
   markup. shell.css already carries the CORRECT versions of all three
   (transform-origin: 889px 741px, matching meter.png's real hub center;
   stroke/fill: #262626 charcoal, not the old brand blue) -- but this
   file loads AFTER shell.css on index.html, so these old, quick-check-
   only values were silently winning the cascade there too, rotating
   the shared needle around the wrong pivot (making it swing off the
   visible dial entirely) and, where visible at all, painting it the
   wrong color. Scoped to .qc-page so quick-check.html's own separate,
   still pre-photographic-dial gauge (see its own inline SVG, values
   tuned for THIS 60/70 coordinate system) keeps its exact current
   appearance, while index.html correctly falls through to shell.css's
   values instead of silently overriding them. */
.qc-page .gauge-analog-needle-group {
  transform-origin: 60px 70px;
  animation: gauge-search 7.5s ease-in-out infinite;
  transition: opacity 0.5s ease;
}

.qc-page .gauge-analog-needle {
  stroke: var(--accent);
  stroke-width: 2.5;
  stroke-linecap: round;
}

.qc-page .gauge-analog-hub {
  fill: var(--accent);
}

@keyframes gauge-search {
  0%   { transform: rotate(calc(var(--needle-center, 0deg) + var(--needle-amp, 1) * -24deg)); }
  14%  { transform: rotate(calc(var(--needle-center, 0deg) + var(--needle-amp, 1) * 11deg)); }
  27%  { transform: rotate(calc(var(--needle-center, 0deg) + var(--needle-amp, 1) * -9deg)); }
  41%  { transform: rotate(calc(var(--needle-center, 0deg) + var(--needle-amp, 1) * 23deg)); }
  55%  { transform: rotate(calc(var(--needle-center, 0deg) + var(--needle-amp, 1) * -27deg)); }
  68%  { transform: rotate(calc(var(--needle-center, 0deg) + var(--needle-amp, 1) * 5deg)); }
  82%  { transform: rotate(calc(var(--needle-center, 0deg) + var(--needle-amp, 1) * -15deg)); }
  100% { transform: rotate(calc(var(--needle-center, 0deg) + var(--needle-amp, 1) * -24deg)); }
}

/* Scoped to .qc-page for the same reason as the block above: this rule
   only ever produced a correct result for quick-check.html anyway (its
   real settled angle already comes from the more specific .qc-page.
   reduced-motion override further down this file, which needs THIS
   rule alongside it only for the shared animation:none -- see that
   rule's own comment). Left unscoped, it also matched index.html's
   shared gauge under reduced motion, permanently pinning its needle to
   a decorative 0deg regardless of any real result -- shell.css already
   has the correct, generic @media(prefers-reduced-motion) rule for the
   shared gauge (rotate(var(--needle-center, 0deg))), which this scoping
   now lets apply uncontested on index.html. */
.qc-page.reduced-motion .gauge-analog-needle-group {
  animation: none;
  transform: rotate(0deg);
}

/* ───────── Gauge: dormant / unknown state ─────────
   "No evidence = no verdict" (see the big comment above). Default state
   for the meter itself AND its disclosure panel -- reachable with zero
   JS (see .gauge-dormant in both pages' own static HTML); only
   header-gauge.js's activate() ever removes it, and only once real
   evidence exists. The needle is fully HIDDEN, not merely dimmed --
   "strong preference: no visible needle until a suitability value
   actually exists" -- while the arc/labels stay visible but visibly
   muted, so the header never looks incomplete or broken, only quietly
   awaiting evidence. A slow, subtle breathing opacity on the arc alone
   is the "intentionally awaiting evidence, not disabled" cue; the
   needle itself has nothing to animate while it's invisible, so its
   own perpetual gauge-search animation is explicitly paused rather than
   left running unseen. */
/* Scoped to .qc-page -- quick-check.html's own separate gauge is
   deliberately unaffected by the "needle stays visible + hunting while
   dormant" behavior shell.css now uses for the shared gauge (see
   shell.css's own .header-gauge.gauge-dormant .gauge-face comment);
   left unscoped, this rule also hid the shared gauge's needle on
   index.html regardless of --needle-center, which is exactly the
   Atrium's reported "dormant with no needle" symptom even once a real
   result existed and the .gauge-dormant class had correctly already
   been removed -- the needle group itself was rotating around the
   right angle at that point, just still forced invisible by this
   stale, unscoped rule. */
.qc-page .header-gauge.gauge-dormant .gauge-analog-needle-group {
  opacity: 0;
  animation-play-state: paused;
}

.header-gauge.gauge-dormant .gauge-analog-arc {
  opacity: 0.3;
  filter: saturate(0.35);
  animation: gauge-dormant-breathe 4.5s ease-in-out infinite;
}

.header-gauge.gauge-dormant .gauge-analog-label {
  opacity: 0.4;
}

@keyframes gauge-dormant-breathe {
  0%, 100% { opacity: 0.22; }
  50% { opacity: 0.36; }
}

body.reduced-motion .header-gauge.gauge-dormant .gauge-analog-arc {
  animation: none;
  opacity: 0.3;
}

/* ───────── Entry paths, inside the decision card ───────── */

/* Side by side, not stacked -- the decision card is a wide, centered
   surface (max-width: 620px), so the base .landing-entry-grid 2-column
   behavior fits comfortably and matches "Quick Check / Detailed
   Assessment" reading as one line. Gap/padding are now plain, fixed
   values -- no more compact-state override to transition into, since
   the decision card is ordinary document flow and simply gets as tall
   as its content needs (the page scrolls if that's more than one
   screenful, per direct instruction not to aggressively shrink this
   composition to force it onscreen). */
.hero-entry .landing-entry-grid {
  margin: 0;
  gap: 0.6rem;
}

.hero-entry .landing-entry-card {
  padding: 1rem 1.15rem;
}

.landing-entry-note {
  font-size: 0.78rem;
  color: var(--ink-muted);
  margin: 0 0 var(--space-sm);
}

.hero-entry-footnote {
  font-size: 0.78rem;
  color: var(--ink-muted);
  text-align: center;
  margin: 0.75rem 0 0;
}

/* ───────── Clarity cards: understanding, not noise ─────────
   "The two worlds need stronger visual contrast... Internet noise ->
   understanding -> CheckMyARM guidance." Deliberately the visual
   OPPOSITE of the noise field above: ONE consistent dark surface
   (never a different material per card, unlike the noise field's own
   per-fragment color chaos), NEVER rotated (the noise field's own
   fragments are; these are calm, level, stable), ONE consistent
   typographic system throughout. Distinctness between cards comes
   entirely from a single accent color per card (a left border stripe +
   the title's own color, see .clarity-card-accent-* below) -- a
   restrained, CheckMyARM-adjacent palette (muted blue/teal/green/amber/
   rose/violet, not the noise field's own vivid sticky-note hues) so
   "calm" doesn't collapse into "gray and boring." Pronounced corner
   radius, generous internal padding, and a real two-layer drop shadow
   (unchanged from before) still read as "a physical object resting
   above the page," just without the color-per-material chaos of the
   fragments above. Still reveals via the same generic .reveal
   mechanism used elsewhere in this file. */

.clarity-card {
  background: #161d2c;
  border: 1px solid #212d42;
  border-left-width: 4px;
  border-radius: 12px;
  padding: calc(1.1rem * var(--ui-scale)) calc(1.15rem * var(--ui-scale));
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.35), 0 14px 28px rgba(0, 0, 0, 0.38), 0 1px 0 rgba(255, 255, 255, 0.03) inset;
  transition: border-color 0.2s ease;
}

.clarity-card h3 {
  font-size: calc(0.92rem * var(--ui-scale));
  font-weight: 700;
  letter-spacing: -0.005em;
  margin: 0 0 0.4rem;
  padding-bottom: 0.4rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.clarity-card p {
  font-size: calc(0.78rem * var(--ui-scale));
  line-height: 1.5;
  color: var(--ink-secondary);
  margin: 0;
}

/* Six accent themes -- a restrained, coherent CheckMyARM-adjacent
   palette, deliberately calmer/muted next to the desk/props scene's own
   real photographic color and texture so the same "this page has color"
   quality survives into the calm half of the page without the two
   worlds reading as the same design system. */
.clarity-card-accent-amber { border-left-color: #d9a441; }
.clarity-card-accent-amber h3 { color: #e8bc6a; }
.clarity-card-accent-teal { border-left-color: #2dd4bf; }
.clarity-card-accent-teal h3 { color: #5eead4; }
.clarity-card-accent-rose { border-left-color: #fb7185; }
.clarity-card-accent-rose h3 { color: #fca5b1; }
.clarity-card-accent-violet { border-left-color: #a78bfa; }
.clarity-card-accent-violet h3 { color: #c4b5fd; }
.clarity-card-accent-blue { border-left-color: #60a5fa; }
.clarity-card-accent-blue h3 { color: #93c5fd; }
.clarity-card-accent-green { border-left-color: #4ade80; }
.clarity-card-accent-green h3 { color: #86efac; }

/* "The colored accent should visually lead from each rail card toward
   its explainer" -- the explainer opens toward each card's own INSIDE
   (center-facing) edge (see .edu-panel.explainer-edge-*, below), so the
   card's own accent should sit on that same inside edge, not always on
   the left. The base rules above are already correct for RIGHT-rail
   cards ("right-side cards get accent on LEFT/inside edge" -- no change
   needed there). LEFT-rail cards get the accent flipped onto their
   RIGHT edge instead: reset the left edge back to the plain default
   border, move the 4px accent width to the right, and re-point each of
   the six accent colors there. Scoped by #exhibit-rail-left's ID, which
   safely outranks the unscoped base rules' plain class selectors. */
#exhibit-rail-left .clarity-card {
  border-left-width: 1px;
  border-right-width: 4px;
}
#exhibit-rail-left .clarity-card-accent-amber { border-left-color: #212d42; border-right-color: #d9a441; }
#exhibit-rail-left .clarity-card-accent-teal { border-left-color: #212d42; border-right-color: #2dd4bf; }
#exhibit-rail-left .clarity-card-accent-rose { border-left-color: #212d42; border-right-color: #fb7185; }
#exhibit-rail-left .clarity-card-accent-violet { border-left-color: #212d42; border-right-color: #a78bfa; }
#exhibit-rail-left .clarity-card-accent-blue { border-left-color: #212d42; border-right-color: #60a5fa; }
#exhibit-rail-left .clarity-card-accent-green { border-left-color: #212d42; border-right-color: #4ade80; }

/* .clarity-card-link is the same physical object, just an <a> to an
   existing page instead of a static exhibit -- "some should simply
   point toward existing educational content" (why.html/how-it-
   thinks.html/privacy.html, all real pages this site already has, not
   fabricated new content). Resets the browser's own anchor defaults
   (underline, link-blue) so it reads as the identical plaque, not a
   normal hyperlink; a small always-visible "->" glyph and a subtle
   hover/focus lift are the only things distinguishing it from a static
   card at rest. The arrow stays a plain, neutral light gray regardless
   of which accent the card landed on -- simpler and more robust than
   trying to keep six separate arrow colors in sync with six accent
   classes. */
.clarity-card-link {
  display: block;
  text-decoration: none;
  color: inherit;
}

.clarity-card-link p span {
  color: var(--ink-primary);
  margin-left: 0.15em;
}

/* ───────── Education card expansion panels: the center-stage explainer ─────────
   Each panel is still a real nested DOM child of its own .clarity-card
   (never a portal -- see wireDisclosure()'s containment checks,
   education-cards.js, which are what make that safe now that the panel
   floats well outside the card's own visual box) -- renderEducationCards()
   (education-cards.js).

   Replaces the prior "float beside the card via position:absolute;
   left/right:100%" model, which real-world testing (a 13" laptop at
   normal Windows scaling, and especially browser zoom) found could clip
   against the viewport, cover unrelated content, or become partially
   inaccessible. Now UNCONDITIONALLY position:fixed against the true
   viewport, at every width -- "primarily determined by viewport safety,
   not by matching the source card's Y position" -- with left/top/
   max-width/max-height computed fresh on every open by
   positionExplainer() (education-cards.js). Below the dock breakpoint
   (or whenever the dock composition itself is too cramped for a side
   bias to make sense), that same function centers the panel with no
   bias and no connector -- "a centered modal/sheet-style explainer,"
   the same presentation at every width, just without the connector
   line's implied relationship to a specific fixed-position card. */
.edu-panel {
  position: fixed;
  z-index: 75;
  left: 50%;
  top: 50%;
  margin: 0;
  transform: translate(-50%, -50%) scale(0.97);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  overflow-y: auto;
  transition: opacity 0.18s ease, transform 0.18s ease, visibility 0s linear 0.18s;
}

/* Reads a class on the PANEL itself, not a ".clarity-card.disclosure-open
   .edu-panel" descendant selector -- the panel now lives in a shared,
   untransformed portal (education-cards.js), not nested inside its own
   card, so a descendant combinator could never match here. */
.edu-panel.disclosure-open {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, -50%) scale(1);
  pointer-events: auto;
  transition: opacity 0.18s ease, transform 0.18s ease;
}

/* The connector -- "a short/subtle line from source card toward its
   explainer... approximately horizontal when geometry permits... NOT a
   giant diagonal." One shared, viewport-covering SVG overlay (injected
   once by ensureExplainerConnector(), education-cards.js), its single
   <line>'s endpoints recomputed on every open by positionExplainer(),
   which now anchors the panel near the card's OWN side and targets the
   line's far end at the panel's near edge (not its center) -- the
   geometry itself is what keeps this short and close to horizontal;
   thinner/more transparent than the prior pass on top of that, so even
   the rare longer line (a card far from vertical center) reads as
   quiet, not a bold diagonal stripe across the page. Sits above the
   docked cards (z-index 8) but below the panel itself (75).
   hideExplainerConnector() (education-cards.js) is called from BOTH
   wireDisclosure()'s doClose() and the "another disclosure is opening"
   path inside doOpen() -- the connector can never outlive its own
   panel's open state, by construction, not by convention. */
.edu-explainer-connector {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  z-index: 74;
  pointer-events: none;
  display: none;
}

.edu-explainer-connector-line,
#edu-explainer-connector-line {
  /* Solid, not dashed -- "a much more local relationship," and a dashed
     line read as busier/more attention-grabbing than the short link
     this is meant to be. Weight/opacity bumped up from an earlier
     pass's hairline (1px/0.35) -- "more deliberate... moderately
     visible" -- still a quiet, local line, not an attention-grabbing
     one, but no longer easy to miss entirely. Stroke color is still set
     inline (positionExplainer(), education-cards.js) to the source
     card's own accent, unchanged. */
  stroke-width: 2;
  opacity: 0.55;
}

body.reduced-motion .edu-explainer-connector {
  display: none !important;
}

.edu-panel-inner {
  position: relative;
  background: rgba(15, 23, 42, 0.98);
  border: 1px solid #2a3750;
  border-radius: 14px;
  padding: 1.4rem 1.5rem;
  /* Plain, neutral elevation only -- "keep the main explainer surface
     dark/neutral... do NOT heavily tint the whole explainer or add a
     giant glow." A prior pass's accent-colored glow layers were removed
     outright; the accent relationship now lives ONLY in the edge border
     and title color below, both driven by the same --explainer-
     accent-rgb custom property positionExplainer() (education-cards.js)
     already sets on the panel. */
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.55), 0 6px 16px rgba(0, 0, 0, 0.4);
}

/* "Color the vertical edge of the explainer that faces its source
   card": positionExplainer() adds explainer-edge-left/-right to the
   PANEL (not the inner card) based on which rail the source card came
   from -- left source -> accent the LEFT edge, right source -> accent
   the RIGHT edge. Uses --explainer-accent-rgb directly rather than a
   six-color table keyed to .clarity-card-accent-* -- the panel now
   lives in a shared portal, detached from its card's own accent class,
   so a ".clarity-card-accent-X .edu-panel-inner" descendant selector
   (this file's prior approach) could never match there; this is also
   the direct fix for that dead rule. */
.edu-panel.explainer-edge-left .edu-panel-inner {
  border-left: 3px solid rgb(var(--explainer-accent-rgb, 96, 165, 250));
}

.edu-panel.explainer-edge-right .edu-panel-inner {
  border-right: 3px solid rgb(var(--explainer-accent-rgb, 96, 165, 250));
}

/* "Render the explainer title in the same accent color as the source
   card title/accent" -- completes the colored-edge -> connector ->
   colored-edge/title chain the brief describes, using the identical
   custom property the edge border and connector line already read. */
.edu-panel-inner h4 {
  font-size: 0.98rem;
  font-weight: 700;
  /* Right margin was 1.4rem, reserving room for the now-removed close
     (X) button -- back to a plain, symmetric margin now that nothing
     needs to be avoided. */
  margin: 0 0 0.6rem;
  color: rgb(var(--explainer-accent-rgb, 96, 165, 250));
}

.edu-panel-inner p {
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--ink-secondary, #cbd5e1);
  margin: 0 0 0.65rem;
}

.edu-panel-inner p:last-of-type {
  margin-bottom: 0;
}

.edu-panel-why {
  font-size: 0.8rem;
  color: var(--ink-muted, #94a3b8);
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  padding-top: 0.55rem;
  margin-top: 0.4rem !important;
}

.edu-panel-link {
  display: inline-block;
  margin-top: 0.4rem;
  font-size: 0.83rem;
  font-weight: 700;
  color: var(--accent-soft, #93c5fd);
  text-decoration: none;
}

.edu-panel-link:hover,
.edu-panel-link:focus-visible {
  text-decoration: underline;
}

.edu-panel-freshness {
  font-size: 0.7rem;
  color: var(--ink-muted, #94a3b8);
  margin-top: 0.45rem !important;
}

/* .edu-panel-close (the X button) was removed outright -- the
   originating card is already the toggle, and a second, separate close
   affordance read as unnecessary/misleading. Escape, clicking elsewhere,
   and mouseleave/blur (wireDisclosure()'s existing close paths,
   education-cards.js) are unaffected; .term-panel-close (the small
   terminology-chip popover, a different UI surface) is untouched. */

body.reduced-motion .edu-panel {
  transition: none !important;
}

/* Sensible fallback sizing before positionExplainer() (education-cards.js)
   sets real, viewport-measured inline max-width/max-height on open --
   also the effective size for the connector-free centered-modal case,
   which relies on these same defaults rather than a separate rule set. */
.edu-panel {
  width: 400px;
  max-width: min(560px, calc(100vw - 3rem));
  max-height: calc(100vh - 3rem);
}

/* ROOT-CAUSE FIX — the fresh-load "first sentence looks smaller/
   differently positioned, then jumps slightly on the first scroll"
   symptom. Traced (not guessed at, see index.html's own init-sequence
   comment) to CSS transitions firing during the page's very FIRST style
   establishment: this document is tall enough that the browser can
   paint at least one frame of "as parsed so far" content before the
   blocking inline script at the end of body finishes running and
   applies its own custom properties/classes -- meaning several
   transition-bearing rules here (`.hero-stage`, `.teleprompter`,
   `#hero-entry`, `.landing-entry-card`, and any other `.reveal`/
   `.js-ready`-scoped rule) could animate FROM that stale pre-script
   frame TO the real, JS-established state on load, a one-time
   "settling" motion that never recurs once the state is already
   correct (exactly matching "looks right after scrolling down and back
   up"). Fixing this by scoping every individual transition declaration
   to a second marker would mean touching a dozen separate rules and
   staying disciplined about it forever after; this one blanket
   override is simpler and can't be forgotten: while `body.js-ready` is
   present but `body.js-settled` isn't yet (added via a double
   requestAnimationFrame after the init sequence finishes, index.html/
   quick-check.html -- see wireSettleMarker()), NOTHING transitions,
   so the very first script-established frame renders its correct,
   final geometry directly, with nothing to animate FROM. Ordinary
   scroll-driven and hover/focus changes are unaffected -- js-settled
   is true within roughly one frame of load, long before a visitor can
   scroll or interact. */
body.js-ready:not(.js-settled) * {
  transition: none !important;
}

/* Scroll reveal -- generic, reused by .clarity-card here and previously
   by other sections. Strict progressive enhancement: elements only ever
   start hidden once body.js-ready is present (set synchronously by the
   page's own script), so a visitor without JS sees full content
   immediately. rotate(var(--card-rotate, 0deg)) is kept in this
   composed transform even though no rule sets --card-rotate anymore
   (a prior pass's own per-card alternating tilt was removed outright --
   "these are calm, level, stable," explicitly the opposite of the now-
   rotated noise field) -- harmless at its 0deg default, and keeping the
   custom-property seam here (rather than deleting it) means a future
   pass could reintroduce per-card variance the same safe way, without
   repeating the exact specificity bug a custom property was originally
   added here to fix: this selector's specificity (body + 2 classes)
   beats .clarity-card:nth-child(...)'s own (1 class + 1 pseudo-class),
   so a literal `transform: rotate(...)` declared there would be
   silently discarded by this rule, both before and after reveal. */
body.js-ready .reveal {
  opacity: 0;
  transform: translateY(16px) rotate(var(--card-rotate, 0deg));
  transition: opacity 0.6s ease, transform 0.6s ease;
}

body.js-ready .reveal.revealed {
  opacity: 1;
  transform: translateY(0) rotate(var(--card-rotate, 0deg));
}

/* Future-interaction hook, this pass -- "structure the cards so they
   can later support... hover/focus/tap expansion... work for mouse,
   keyboard focus, and touch." No expansion panel exists yet (explicitly
   deferred -- see the HTML comment above the exhibit rails); this is
   only the visible, always-present affordance that something here is
   interactive, so a later pass can attach real behavior without also
   needing to retrofit focusability. Applies uniformly to every
   .clarity-card (the five carrying tabindex="0" AND the three real
   .clarity-card-link anchors, which were already natively focusable) --
   one consistent rule instead of two near-duplicate ones. :active is
   included alongside :hover/:focus-visible specifically for touch,
   which doesn't reliably produce a hover or focus state on tap. */
.clarity-card:hover,
.clarity-card:focus-visible,
.clarity-card:active {
  border-color: rgba(255, 255, 255, 0.4);
  outline: none;
}

.clarity-card[tabindex="0"] {
  cursor: pointer;
}

@media (prefers-reduced-motion: reduce) {
  body.js-ready .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* ───────── Footer links ───────── */

.experience-footer-links {
  margin-bottom: 0.5rem;
}

.experience-footer-links a {
  color: var(--ink-muted);
  text-decoration: none;
}

.experience-footer-links a:hover {
  color: var(--accent-soft);
}

.experience-footer-links span {
  margin: 0 0.5rem;
  color: #334155;
}

/* Small tier only (<768px, see the "three responsive tiers" note above
   .props-stage) -- the hero stops being sticky/fixed at all, falling
   into plain normal document flow: hero -> actions -> terminology ->
   education cards, top to bottom, no pinned camera. */
@media (max-width: 767px) {
  .hero-stage {
    position: relative;
    top: 0;
    max-width: none;
    width: 100%;
    padding: 4rem 0 2rem;
  }
}

@media (max-width: 560px) {
  /* :not(.scene-act1) -- a real bug found live, not shipped blind: this
     rule and .scene-act1 (below) have equal specificity (one class
     each), and this one comes later in the file, so at narrow widths it
     was silently overriding .scene-act1's own clamp() sizing down to a
     flat 1.2rem (19.2px) -- undoing "Acts 1-3 substantially larger and
     bolder" at exactly the viewport widths a phone visitor would see.
     clamp(2.1rem, 6vw, 3.6rem) already has its own responsive floor
     built in and doesn't need this rule at all; scoping it away from
     .scene-act1 leaves it doing only what it originally applied to
     (nothing currently uses bare .scene-line without also carrying
     either .scene-act1 or .scene-mission-line, so this is effectively
     inert now -- kept rather than deleted, in case a future line is
     added that genuinely wants this fallback size). */
  .scene-line:not(.scene-act1) {
    font-size: 1.2rem;
  }
  .scene-mission-line {
    font-size: 1.15rem;
  }
  .scene-sub {
    font-size: 0.9rem;
  }
}

/* The "Continue banner" (.landing-continue/.landing-continue-text/
   .landing-continue-link) that used to live here was removed outright,
   not just unused -- see CLAUDE.md, "Atrium/Quick Check navigation
   cleanup." The two entry cards below are now fully state-aware and
   already communicate exactly what this banner used to, making a
   second, separate "assessment in progress" affordance redundant. */

/* ───────── Entry cards ─────────
   Calmer than a typical pricing-card duo on purpose: quieter borders,
   more internal room, no icons -- the two paths are differentiated by
   typography and copy alone, matching the Instrument's own restraint.

   Rewritten this pass to remove an implied preference the old
   .primary/.secondary split carried (filled blue button + blue border
   on one card, plain gray border + outlined button on the other --
   Quick Check reading as the "real" choice and Detailed Assessment as
   a fallback). Both cards now share the IDENTICAL structural rule
   below and differ only through one custom property, --entry-accent,
   set by .entry-quick (green) / .entry-detailed (blue) -- same border
   weight, same filled-button treatment, same typography, "two
   different levels of engagement," never preferred-vs-secondary. */

.landing-entry-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-sm);
  margin: 0 0 var(--space-md);
}

.landing-entry-card {
  --entry-accent: #64748b;
  --entry-accent-rgb: 100, 116, 139;
  /* A subtly deeper, warmer-than-.clarity-card background (a faint
     tinted gradient in the card's own accent, not flat navy) plus a
     stronger 2px border -- "the two primary decisions on the page," now
     reading as a genuinely distinct surface from the surrounding
     education/terminology cards, not a fractionally-different variant
     of them. */
  background: linear-gradient(165deg, rgba(var(--entry-accent-rgb), 0.09), rgba(15, 23, 42, 0.55));
  border: 2px solid var(--entry-accent);
  border-radius: 12px;
  padding: var(--space-md) var(--space-md) var(--space-md);
  display: flex;
  flex-direction: column;
}

.landing-entry-card.entry-quick {
  --entry-accent: #22c55e;
  --entry-accent-rgb: 34, 197, 94;
}

.landing-entry-card.entry-detailed {
  --entry-accent: #3b82f6;
  --entry-accent-rgb: 59, 130, 246;
}

.landing-entry-kicker {
  font-size: 0.74rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--entry-accent);
  margin: 0 0 0.35rem;
}

.landing-entry-card h2 {
  font-size: 1.1rem;
  margin: 0 0 0.2rem;
  font-weight: 700;
  color: var(--ink-primary);
}

.landing-entry-time {
  font-size: 0.78rem;
  color: var(--ink-muted);
  margin-bottom: var(--space-sm);
}

.landing-entry-body {
  color: var(--ink-secondary);
  font-size: 0.9rem;
  line-height: 1.6;
  margin: 0 0 var(--space-sm);
  flex-grow: 1;
}

.landing-entry-btn {
  display: inline-block;
  text-align: center;
  text-decoration: none;
  font-weight: 700;
  font-size: 0.92rem;
  padding: 0.65rem 1rem;
  border-radius: 8px;
  background: var(--entry-accent);
  /* A near-black, not white, label on both fills -- checked for
     contrast on both accent colors (green and blue alike), where a
     plain white label reads noticeably weaker on the green fill
     specifically. Using the identical dark color on both keeps the two
     buttons visually matched, which is the whole point of this pass. */
  color: #08131f;
  filter: brightness(1);
  transition: filter 0.15s ease;
}

.landing-entry-btn:hover {
  filter: brightness(1.12);
}

.landing-detail {
  border: 1px solid #1e293b;
  border-radius: 8px;
  margin-bottom: 0.6rem;
  background: #111a2e;
}

.landing-detail summary {
  cursor: pointer;
  list-style: none;
  padding: 0.9rem 1.1rem;
  font-weight: 600;
  font-size: 0.92rem;
  color: #e2e8f0;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.landing-detail summary::-webkit-details-marker {
  display: none;
}

.landing-detail summary::after {
  content: '+';
  color: #64748b;
  font-size: 1.1rem;
  font-weight: 400;
  margin-left: 1rem;
}

.landing-detail[open] summary::after {
  content: '−';
}

.landing-detail-body {
  padding: 0 1.1rem 1rem;
  color: #94a3b8;
  font-size: 0.88rem;
}

.landing-detail-body a {
  color: #93c5fd;
}

/* ───────── Footer ───────── */

.landing-footer {
  text-align: center;
  color: #475569;
  font-size: calc(0.78rem * var(--ui-scale));
  padding: calc(2rem * var(--ui-scale-deep)) 1rem calc(2.5rem * var(--ui-scale-deep));
}


/* ───────── Quick Check shell ───────── */

.qc-back-link {
  color: #64748b;
  text-decoration: none;
  font-size: 0.82rem;
}

.qc-back-link:hover {
  color: #94a3b8;
}

.qc-card {
  background: #16213a;
  border: 1px solid #334155;
  border-radius: 12px;
  padding: 2rem 1.75rem;
  margin-top: 1.5rem;
  text-align: center;
}

.qc-card h1 {
  font-size: 1.5rem;
  margin: 0.4rem 0 0.25rem;
}

/* .qc-view h1/h2 -- no dedicated rule existed for these before this
   pass, meaning every "Quick Check"/"Checking this PC…"-style heading
   inside #qc-surface rendered at plain browser-default h1/h2 margins
   (roughly 0.67em/0.83em top+bottom at their own font-size) -- a real,
   previously invisible contributor to "vertical gaps remain large" on
   this page specifically, now replaced with compact, scale-aware
   values matching the rest of this surface's typography. */
.qc-view h1,
.qc-view h2 {
  margin: calc(0.4rem * var(--ui-scale)) 0 calc(0.3rem * var(--ui-scale));
}

.qc-view h1 {
  font-size: calc(1.4rem * var(--ui-scale));
}

.qc-view h2 {
  font-size: calc(1.2rem * var(--ui-scale));
}

.qc-time {
  color: #64748b;
  font-size: calc(0.85rem * var(--ui-scale));
  margin-bottom: calc(1.25rem * var(--ui-scale));
}

.qc-card p {
  color: #cbd5e1;
  font-size: 0.92rem;
  max-width: 480px;
  margin: 0 auto 0.9rem;
  text-align: left;
}

.qc-caveat {
  color: #94a3b8;
  font-size: calc(0.85rem * var(--ui-scale));
  background: #111a2e;
  border: 1px solid #1e293b;
  border-radius: 8px;
  padding: calc(0.85rem * var(--ui-scale)) 1rem;
  text-align: left;
  margin: calc(1.1rem * var(--ui-scale)) 0 calc(1.5rem * var(--ui-scale));
}

.qc-btn {
  display: inline-block;
  background: #3b82f6;
  color: #f8fafc;
  text-decoration: none;
  font-weight: 700;
  font-size: 1rem;
  padding: 0.8rem 1.75rem;
  border-radius: 8px;
  border: none;
  cursor: pointer;
}

.qc-btn:hover {
  background: #2563eb;
}

.qc-btn:disabled {
  background: #334155;
  color: #64748b;
  cursor: default;
}

.qc-status {
  margin-top: 1rem;
  font-size: 0.85rem;
  color: #94a3b8;
  min-height: 1.2rem;
}

.qc-status.error {
  color: #f87171;
}

.qc-note {
  font-size: 0.78rem;
  color: #64748b;
  margin-top: 1.5rem;
}

.qc-alt-link {
  color: #93c5fd;
  text-decoration: none;
}

.qc-alt-link:hover {
  text-decoration: underline;
}

/* ───────── Quick Check micro-onboarding modal ─────────
   A lightweight, friendly beginning to Quick Check -- two questions,
   shown once, right before the scanner launches (quick-check.html's own
   startScan()/showOnboardingModal()). Deliberately built from this same
   page's existing .qc-card visual language (dark surface, muted border,
   the same .qc-btn) rather than a new design system -- this is a small
   panel, not a new product surface. */
.qc-modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(8, 12, 22, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  z-index: 100;
}

.qc-modal-backdrop[hidden] {
  display: none;
}

.qc-modal-panel {
  background: #16213a;
  border: 1px solid #334155;
  border-radius: 12px;
  padding: 1.75rem 1.75rem 1.5rem;
  max-width: 460px;
  width: 100%;
  max-height: calc(100vh - 3rem);
  overflow-y: auto;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
}

.qc-modal-panel h2 {
  margin: 0 0 0.35rem;
  font-size: 1.2rem;
  color: #f1f5f9;
}

.qc-modal-lead {
  color: #94a3b8;
  font-size: calc(0.85rem * var(--ui-scale));
  margin: 0 0 calc(1.3rem * var(--ui-scale));
}

.qc-modal-question {
  margin-bottom: calc(1.4rem * var(--ui-scale-deep));
}

.qc-modal-question-label {
  font-size: calc(0.92rem * var(--ui-scale));
  font-weight: 700;
  color: #f1f5f9;
  margin: 0 0 calc(0.25rem * var(--ui-scale));
}

.qc-modal-question-hint {
  font-size: calc(0.78rem * var(--ui-scale));
  color: #64748b;
  margin: 0 0 calc(0.6rem * var(--ui-scale));
}

.qc-modal-choices {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.4rem;
}

.qc-modal-choice {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.82rem;
  color: #cbd5e1;
  background: #111a2e;
  border: 1px solid #1e293b;
  border-radius: 8px;
  padding: 0.5rem 0.6rem;
  cursor: pointer;
}

.qc-modal-choice:has(input:checked) {
  border-color: #3b82f6;
  background: rgba(59, 130, 246, 0.12);
  color: #f1f5f9;
}

.qc-modal-choice input {
  accent-color: #3b82f6;
  flex-shrink: 0;
}

.qc-modal-tolerance {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  margin-top: 0.6rem;
}

.qc-modal-tol-choice {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: baseline;
  column-gap: 0.5rem;
  row-gap: 0.1rem;
  font-size: 0.82rem;
  background: #111a2e;
  border: 1px solid #1e293b;
  border-radius: 8px;
  padding: 0.55rem 0.7rem;
  cursor: pointer;
}

.qc-modal-tol-choice:has(input:checked) {
  border-color: #3b82f6;
  background: rgba(59, 130, 246, 0.12);
}

.qc-modal-tol-choice input {
  grid-row: 1 / 3;
  accent-color: #3b82f6;
}

.qc-modal-tol-label {
  font-weight: 700;
  color: #f1f5f9;
}

.qc-modal-tol-detail {
  grid-column: 2;
  color: #94a3b8;
  font-size: 0.78rem;
}

.qc-modal-actions {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-top: 0.5rem;
}

.qc-modal-actions .qc-btn {
  padding: 0.7rem 1.5rem;
  font-size: 0.92rem;
}

.qc-modal-skip {
  background: none;
  border: none;
  color: #64748b;
  font-size: 0.82rem;
  cursor: pointer;
  padding: 0;
}

.qc-modal-skip:hover {
  color: #94a3b8;
  text-decoration: underline;
}

@media (max-width: 560px) {
  .qc-modal-choices {
    grid-template-columns: 1fr;
  }
  .qc-modal-panel {
    padding: 1.4rem 1.25rem 1.25rem;
  }
}

/* ───────── Quick Check results page (Voice & Visual Hierarchy pass) ───────── */
/* A short conversation, not a report: a vertical run of prose beats with
   real breathing room between them, not a grid of labeled cards. Tone
   colors stay semantic and are now used sparingly -- only on the demoted
   "see the numbers" detail, never as the primary visual signal, since the
   words themselves now carry the verdict. */

.qc-tone-good { color: #4ade80; }
.qc-tone-caution { color: #fbbf24; }
.qc-tone-poor { color: #f87171; }
.qc-tone-unknown { color: #94a3b8; }

.qc-result-eyebrow {
  font-size: calc(0.78rem * var(--ui-scale));
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #64748b;
  margin-bottom: calc(0.6rem * var(--ui-scale));
}

.qc-conv-open {
  text-align: center;
  padding: calc(1.25rem * var(--ui-scale)) 0 calc(0.5rem * var(--ui-scale));
}

/* The verdict itself -- sized like a real headline moment (between the
   page's h1-scale and ordinary body text), never a labeled data field. */
.qc-conv-lead {
  font-size: calc(1.5rem * var(--ui-scale));
  font-weight: 700;
  line-height: 1.35;
  color: #f1f5f9;
  max-width: 480px;
  margin: 0 auto;
}

/* Each beat gets real air around it -- this margin, more than any single
   color or border, is what turns a stack of facts into a conversation
   with a pace to it. Wrapped in var(--ui-scale) -- with up to five of
   these stacked in a real result, this single margin is the single
   largest contributor to the result view's own vertical footprint, and
   the main reason a constrained viewport needed real scrolling before
   this pass. */
.qc-conv-block {
  margin: calc(2.25rem * var(--ui-scale-deep)) 0;
}

.qc-conv-block h2 {
  font-size: calc(1.1rem * var(--ui-scale));
  font-weight: 700;
  color: #e2e8f0;
  margin: 0 0 calc(0.6rem * var(--ui-scale));
}

.qc-conv-block p {
  color: #cbd5e1;
  font-size: calc(0.96rem * var(--ui-scale));
  line-height: 1.65;
  margin: 0;
}

.qc-conv-list {
  list-style: none;
  margin: calc(0.85rem * var(--ui-scale)) 0 0;
  padding: 0;
  font-size: calc(0.92rem * var(--ui-scale));
  color: #e2e8f0;
}

.qc-conv-list li {
  padding: 0.3rem 0;
  border-bottom: 1px solid #1e293b;
}

.qc-conv-list li:last-child {
  border-bottom: none;
}

.qc-more {
  color: #64748b;
  font-size: 0.82rem;
}

.qc-kind-tag {
  font-size: 0.7rem;
  color: #64748b;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin-left: 0.3rem;
}

.qc-conv-next p + p {
  margin-top: 0.85rem;
}

.qc-conv-note {
  color: #64748b !important;
  font-size: 0.85rem !important;
  font-style: italic;
}

.qc-result-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  justify-content: center;
  margin: calc(1.75rem * var(--ui-scale)) 0 calc(1.5rem * var(--ui-scale));
}

.qc-result-buttons .qc-btn {
  padding: calc(0.75rem * var(--ui-scale)) calc(1.4rem * var(--ui-scale));
  font-size: calc(0.92rem * var(--ui-scale));
}

.qc-btn-secondary {
  background: transparent;
  color: #93c5fd;
  border: 1px solid #3b82f6;
}

.qc-btn-secondary:hover {
  background: #1e293b;
}

/* Demoted, not deleted -- reuses the exact same quiet accordion pattern
   as the landing page's own below-the-fold detail sections, so the raw
   tier/confidence numbers a power user wants are one click away without
   ever competing with the conversation above for attention. */
.qc-numbers-detail {
  max-width: 420px;
  margin: 0 auto 2.5rem;
}

.qc-numbers-detail .landing-detail-body {
  font-size: 0.85rem;
}

.qc-numbers-detail .landing-detail-body > div {
  padding: 0.2rem 0;
}

@media (max-width: 560px) {
  .qc-conv-lead {
    font-size: 1.25rem;
  }
  .qc-result-buttons {
    flex-direction: column;
  }
  .qc-result-buttons .qc-btn {
    width: 100%;
  }
}

/* ═════════════════════════════════════════════════════════════════
   QUICK CHECK LANDING INTEGRATION — see CLAUDE.md, Quick Check
   integration + responsive polish pass, and "Quick Check mountable-
   component refactor." Quick Check now runs from exactly ONE
   implementation (quick-check-ui.js), mounted either into
   quick-check.html's own standalone page OR directly into the settled
   Atrium (index.html) -- Quick Check reads as a MODE of whichever page
   it's running in, occupying the exact center-stage slot the two action
   cards normally live in on the Atrium, or the equivalent slot on the
   standalone page. Most rules below (.qc-surface/.qc-view/etc.) are
   already plain, unscoped classes -- they apply identically wherever
   the component is mounted, by design. A few (further down) are
   deliberately SCOPED to .qc-page (quick-check.html's own body class),
   where quick-check.html's own separate, standalone header markup is
   what they style -- see each one's own comment for why. */

.qc-surface {
  background: #111a2e;
  border: 1px solid #263449;
  border-radius: 16px;
  padding: calc(2rem * var(--ui-scale-deep)) calc(1.75rem * var(--ui-scale-deep));
  text-align: center;
  position: relative;
  max-width: 640px;
  margin: 0 auto;
}

.qc-view[hidden] {
  display: none;
}

.qc-back-link {
  display: inline-block;
  margin-bottom: 1rem;
}

/* FOCUS MODE — while onboarding/scanning genuinely needs the visitor's
   attention, the surrounding education rails/terminology row and the
   Detailed Assessment link recede (dim, not disappear) rather than
   compete with it; all return to normal prominence once a result exists
   or the visitor is back at the resting start view. */
.qc-detail-link,
#terms-row {
  transition: opacity 0.5s ease, filter 0.5s ease;
}

/* No longer .qc-page-scoped -- see CLAUDE.md, "Quick Check mountable-
   component refactor." #exhibit-rail-left/-right only ever exist on
   quick-check.html and index.html (the only two pages with exhibit
   rails at all), and body.qc-focus is only ever toggled by Quick
   Check's own showView() (quick-check-ui.js) on either host -- so
   dropping the .qc-page requirement here is safe by construction: it
   can never newly match anything on a third page. CORRECTION (see
   CLAUDE.md, "Header Normalization / Quick Check embedding"): live
   testing found this dimming is actually WRONG for the embedded
   Atrium -- the exhibit rails are persistent room chrome there, not a
   "rest of the page" that should recede, and CSS filter (below) has a
   real, damaging side effect on them specifically: it establishes a
   new containing block for their position:fixed .clarity-card
   children per spec, silently repositioning the whole stack relative
   to the rail's own (mostly off-screen) document-flow box instead of
   the viewport -- this is why the rails were visibly jumping to the
   bottom of the page, not merely dimming. Fixed at the SOURCE, not
   here: quick-check-ui.js's mount() now takes a dimEnvironment config
   flag (default true, matching this rule's original intent for
   standalone quick-check.html) that the Atrium's own
   launchQuickCheckInAtrium() sets false, so body.qc-focus is simply
   never added while embedded -- this rule still exists, still
   correctly dims quick-check.html's own rails, and can never fire in
   the Atrium by construction, with no second copy of it needed. */
#exhibit-rail-left,
#exhibit-rail-right {
  transition: opacity 0.5s ease, filter 0.5s ease;
}

body.qc-focus .qc-detail-link,
body.qc-focus #terms-row,
body.qc-focus #exhibit-rail-left,
body.qc-focus #exhibit-rail-right {
  opacity: 0.32;
  filter: saturate(0.55);
}

/* Embedded-in-Atrium legibility override — quick-check-ui.js renders the
   identical .qc-back-link/.qc-note markup whether mounted here or on
   standalone quick-check.html, but the Atrium mounts it directly over
   the busy desk/workspace photography rather than over a plainer
   backdrop, where the shared component's own low-key --ink-muted
   (#64748b) treatment reads as effectively invisible. Scoped by
   CONTAINER (#atrium-action-stage only ever exists on this page, and
   only ever wraps the mounted surface once embedded), not by a new
   body/JS class -- the same component, the same markup, a different
   legibility need purely because of which container it happens to be
   rendered into, resolved entirely in CSS with no second JS flow.
   Follows this page's own established text-shadow-over-glow
   convention for text sitting directly over this same photography
   (see .scene-line/.scene-sub, above) rather than inventing a new
   pill/box treatment. Standalone quick-check.html is completely
   unaffected -- it has no #atrium-action-stage element at all. */
#atrium-action-stage .qc-back-link,
#atrium-action-stage .qc-note,
#atrium-action-stage .qc-alt-link {
  color: var(--ink-secondary, #cbd5e1);
  text-shadow: 0 1px 10px rgba(15, 23, 42, 0.95), 0 1px 3px rgba(15, 23, 42, 0.9);
}

#atrium-action-stage .qc-back-link:hover {
  color: var(--ink-primary, #f1f5f9);
}

#atrium-action-stage .qc-alt-link {
  color: var(--accent-soft, #93c5fd);
}

body.reduced-motion .qc-detail-link,
body.reduced-motion #terms-row,
body.reduced-motion #exhibit-rail-left,
body.reduced-motion #exhibit-rail-right,
body.reduced-motion .qc-surface,
body.reduced-motion .qc-warn-block,
body.reduced-motion .header-gauge {
  transition: none !important;
}

/* THE GAUGE — a real role here, not the landing page's decorative
   framing echo. Starts dormant (see .gauge-dormant, above, for the
   actual muted-arc/hidden-needle treatment -- this page's own copy of
   that state lives in its static HTML, not a page-specific class);
   wakes via header-gauge.js's activate() once a real result exists. */
.qc-page .header-gauge {
  position: relative;
  cursor: help;
}

/* A brief, restrained "arrival" pulse the moment a real result settles
   the needle -- "subtly more visually alive/settled when the result
   arrives," matching this codebase's own standing "emphasis, never a
   glow" precedent (see .shortcut-pulse, index.html) rather than
   anything celebratory. */
@keyframes qc-gauge-settle {
  0% { filter: drop-shadow(0 0 0 rgba(147, 197, 253, 0)); }
  35% { filter: drop-shadow(0 0 10px rgba(147, 197, 253, 0.55)); }
  100% { filter: drop-shadow(0 0 0 rgba(147, 197, 253, 0)); }
}

/* Unscoped (was .qc-page .header-gauge.settling) -- see CLAUDE.md, "Quick
   Check mountable-component refactor." .settling is only ever added by
   quick-check-ui.js's own renderResults(), on whichever gauge element it
   was handed via config.gaugeEl -- safe to apply on any page's gauge,
   including the Atrium's shared #shell-header-gauge, which needs this
   exact pulse too now that Quick Check can produce a real result there. */
.header-gauge.settling {
  animation: qc-gauge-settle 0.9s ease-out;
}

/* ROOT-CAUSE FIX -- "Back can disappear above the viewport while
   unused space remains below, and footer requires extra scrolling."
   This page's .hero-stage used to be permanently position:fixed
   (contributing zero real document height of its own), which is
   exactly the "vertical distribution trick" that caused both symptoms
   at once: an artificial min-height (130vh, deleted along with this
   comment) had to be invented purely to give the footer SOME room to
   reach, and a fixed-position box whose own content overflowed its
   available screen space had nowhere to go but get scale-shrunk by JS
   (the now-deleted syncHeroOverflow()) rather than simply being taller
   than one screenful, which a real page is allowed to be.

   Quick Check has no cinematic narrative to pin anything FOR in the
   first place -- there's no reason .hero-stage should be sticky/fixed
   here at all. Overriding the base .hero-stage rule (position: sticky)
   back to plain, ordinary, un-positioned document flow is the entire
   fix: header -> spacer -> Back/qc-surface/context link -> terminology
   -> education content -> footer, in that literal document order, no
   different from any other page on this site. If the content is taller
   than the viewport, the document simply scrolls -- normally, once,
   the whole page, exactly as the task asks for. */
.qc-page .hero-stage {
  position: static;
  top: auto;
}

/* Real settled needle position, even under reduced motion — the general
   landing-page rule (above) hardcodes a fixed decorative angle since
   index.html's gauge never carries a real result; this page's does. */
body.qc-page.reduced-motion .gauge-analog-needle-group {
  transform: rotate(var(--needle-center, 0deg));
}

/* "Almost like a discoverable easter egg" — a quiet hover/focus popover,
   never permanent header text.

   ROOT-CAUSE FIX -- this positioning was tuned for quick-check.html's
   OWN gauge, whose header layout genuinely does have open space to the
   gauge's right (no wide right-side Menu/nav cluster crowding it the
   way the shared shell's header does). Scoped to .qc-page: left
   unscoped, this rule ALSO matched the shared #shell-gauge-explain-
   panel nav.js renders on every room (index.html included, since the
   Header Normalization pass put the gauge on the header's RIGHT edge,
   right before Menu) -- opening it `left: 100%` from THAT gauge pushes
   it straight off the right edge of the viewport, exactly the reported
   "falls outside the viewport" bug. shell.css already has the correct
   version for the shared gauge (opens downward + inward/left, `top:
   calc(100% + 10px); right: 0;`, so the panel extends left from the
   gauge's own right edge, staying inside the header's own width) --
   this scoping just stops this rule from silently overriding it on
   index.html, while quick-check.html keeps its exact current
   rightward-opening panel unchanged. */
.qc-page .gauge-explain-panel {
  position: absolute;
  top: 0;
  left: 100%;
  margin-left: 12px;
  z-index: 60;
  width: 230px;
  max-width: calc(100vw - 2rem);
  background: #0b1220;
  border: 1px solid #3a4a68;
  border-radius: 10px;
  padding: 0.85rem 1rem;
  font-size: 0.82rem;
  line-height: 1.45;
  box-shadow: 0 16px 36px rgba(0, 0, 0, 0.55), 0 4px 12px rgba(0, 0, 0, 0.4);
}

/* Narrow-width fallback -- not enough guaranteed clearance to the
   gauge's right at this width to trust the anchored position; revert
   to the old, safe centered-below-header treatment instead of adding
   more positioning logic to stay viewport-safe. Scoped alongside the
   base rule above, for the same reason. */
@media (max-width: 640px) {
  .qc-page .gauge-explain-panel {
    left: 50%;
    top: calc(100% + 10px);
    margin-left: 0;
    transform: translateX(-50%);
  }
}

/* Atrium-only footer opacity correction -- header-shared.css's
   --hdr-glass-bg token (rgba(15, 23, 42, 0.55), unchanged) is right for
   the shared header everywhere and for the footer over Lab/Study/
   Reading Room's own, darker background plates. The Atrium's own
   settled workspace photo is considerably brighter there, and the same
   0.55 alpha reads as washed-out gray rather than smoked glass against
   it. This is the smallest fix that corrects only that: an Atrium-
   scoped override raising the footer's own background alpha (color/
   blur/border/geometry/content all untouched, still reading var(
   --hdr-blur)/var(--hdr-border-color) from the canonical token file) so
   it perceptually matches the other three rooms' darker footers,
   without touching the shared token itself (which the header, and any
   future room, still reads at its original value) or quick-check.html
   (which renders no .shell-footer at all -- confirmed, it has no
   nav.js/#app-footer of its own -- so this can never affect it). */
body.landing-body .shell-footer {
  background: rgba(15, 23, 42, 0.72);
}

.gauge-explain-panel strong {
  display: block;
  margin-bottom: 0.3rem;
  font-size: 0.88rem;
  color: #e2e8f0;
}

.gauge-explain-panel p {
  margin: 0;
  color: #94a3b8;
}

.gauge-explain-panel[hidden] {
  display: none;
}

/* THE CHECKLIST — five honest milestones, each driven by a real signal
   (see markActive()/markDone(), quick-check.html). Completed steps stay
   visibly checked rather than disappearing, so a fast transition still
   reads as real, felt progress. */
.qc-checklist {
  list-style: none;
  margin: calc(1.4rem * var(--ui-scale-deep)) 0 calc(0.5rem * var(--ui-scale-deep));
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: calc(0.6rem * var(--ui-scale-deep));
  text-align: left;
}

.qc-check-step {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  font-size: calc(0.92rem * var(--ui-scale));
  color: #64748b;
  opacity: 0.55;
  transition: opacity 0.4s ease, color 0.4s ease;
}

.qc-check-step.is-active {
  opacity: 1;
  color: #e2e8f0;
  font-weight: 600;
}

.qc-check-step.is-done {
  opacity: 0.9;
  color: #94a3b8;
}

.qc-check-icon {
  width: 1.15rem;
  height: 1.15rem;
  border-radius: 50%;
  border: 1.5px solid #475569;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.6rem;
  line-height: 1;
  transition: border-color 0.4s ease, box-shadow 0.4s ease, background 0.4s ease;
}

.qc-check-step.is-active .qc-check-icon {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.18);
  animation: qc-pulse 1.6s ease-in-out infinite;
}

.qc-check-step.is-active .qc-check-icon::before {
  content: '●';
  color: var(--accent);
}

.qc-check-step.is-done .qc-check-icon {
  border-color: #22c55e;
  background: #22c55e;
  color: #08131f;
}

.qc-check-step.is-done .qc-check-icon::before {
  content: '✓';
  font-weight: 700;
}

@keyframes qc-pulse {
  0%, 100% { box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.18); }
  50% { box-shadow: 0 0 0 6px rgba(59, 130, 246, 0.06); }
}

body.reduced-motion .qc-check-step.is-active .qc-check-icon {
  animation: none;
}

/* BROWSER/WINDOWS WARNING — reassuring, friendly, not developer jargon.
   Recedes (not removed) once the scanner has actually connected — the
   moment it stops being useful information to act on. */
.qc-warn-block {
  background: rgba(251, 191, 36, 0.08);
  border: 1px solid rgba(251, 191, 36, 0.28);
  border-radius: 10px;
  padding: 0.85rem 1rem;
  margin: 1.1rem 0;
  font-size: 0.85rem;
  line-height: 1.55;
  color: #e2e8f0;
  text-align: left;
  transition: opacity 0.5s ease;
}

.qc-warn-block p {
  margin: 0 0 0.4rem;
}

.qc-warn-block p:last-child {
  margin-bottom: 0;
}

.qc-warn-why {
  color: #94a3b8;
  font-size: 0.8rem;
}

.qc-warn-block.qc-collapsed {
  opacity: 0.4;
}

/* ───────── The result payoff (item 2) ─────────
   "Should feel like the payoff of the Quick Check experience, not a text
   report inserted into a box." Preserves the exact same result LOGIC/
   copy (quick-check.html) -- only the visual hierarchy changed: the
   verdict now commands real attention, and Why/Noticed/Uncertain are
   each their own scannable card instead of plain stacked paragraphs.
   Tone stays conversational -- no numbers, no percentages, no dashboard
   chrome; those still live one click away in the demoted "See the
   numbers" detail, unchanged. */
.qc-verdict-block {
  --qc-tone: #64748b;
  --qc-tone-rgb: 100, 116, 139;
  background: linear-gradient(165deg, rgba(var(--qc-tone-rgb), 0.14), rgba(15, 23, 42, 0.5));
  border: 1.5px solid rgba(var(--qc-tone-rgb), 0.45);
  border-radius: 14px;
  padding: 1.6rem 1.5rem;
  margin-bottom: 1.6rem;
}

/* Existing-ARM-PC acknowledgement (item 5) -- deliberately a DIFFERENT,
   quieter visual language than .qc-verdict-block: a teal informational
   tone, not one of the good/caution/poor/unknown verdict colors, since
   this is CONTEXT about what question is being answered, not itself a
   verdict. Sits above the verdict block as a preface; hidden by default,
   shown only for a reliably-detected ARM64 machine (renderResults()). */
.qc-arm-ack {
  background: rgba(45, 212, 191, 0.1);
  border: 1.5px solid rgba(45, 212, 191, 0.35);
  border-radius: 12px;
  padding: 0.95rem 1.2rem;
  margin-bottom: 1.1rem;
}

.qc-arm-ack p {
  margin: 0;
  font-size: 0.88rem;
  line-height: 1.55;
  color: var(--ink-secondary, #cbd5e1);
}

.qc-verdict-block.qc-tone-good { --qc-tone: #22c55e; --qc-tone-rgb: 34, 197, 94; }
.qc-verdict-block.qc-tone-caution { --qc-tone: #eab308; --qc-tone-rgb: 234, 179, 8; }
.qc-verdict-block.qc-tone-poor { --qc-tone: #ef4444; --qc-tone-rgb: 239, 68, 68; }
.qc-verdict-block.qc-tone-unknown { --qc-tone: #94a3b8; --qc-tone-rgb: 148, 163, 184; }

.qc-verdict-eyebrow {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--qc-tone);
  margin-bottom: 0.5rem;
}

.qc-verdict-lead {
  font-size: 1.35rem;
  font-weight: 700;
  line-height: 1.4;
  color: var(--ink-primary, #f1f5f9);
  margin: 0;
}

.qc-evidence-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0.9rem;
  margin-bottom: 1.6rem;
  text-align: left;
}

.qc-evidence-card {
  background: #141d31;
  border: 1px solid #263449;
  border-radius: 12px;
  padding: 1.1rem 1.2rem;
}

.qc-evidence-card h3 {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.92rem;
  font-weight: 700;
  color: var(--ink-primary, #f1f5f9);
  margin: 0 0 0.55rem;
}

.qc-evidence-icon {
  font-size: 1rem;
  line-height: 1;
}

.qc-evidence-card p {
  margin: 0;
  font-size: 0.86rem;
  line-height: 1.55;
  color: var(--ink-secondary, #cbd5e1);
}

@media (max-width: 640px) {
  .qc-surface {
    padding: 1.5rem 1.1rem;
  }
  .qc-evidence-grid {
    grid-template-columns: 1fr;
  }
}

/* ───────── Entry-card pre-navigation transition (index.html) ─────────
   "Establish the chosen path" before actually navigating to Quick Check
   or Detailed Assessment — the clicked card grows/brightens slightly,
   its sibling recedes, for a brief, non-theatrical moment before the
   real navigation happens. See wireEntryCardActivation(), index.html.
   Skipped entirely for reduced-motion (navigation is instant there). */
.landing-entry-card.qc-chosen {
  transform: scale(1.035);
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.45);
  transition: transform 0.22s ease, box-shadow 0.22s ease;
  z-index: 2;
}

.landing-entry-card.qc-receded {
  opacity: 0.4;
  transform: scale(0.97);
  transition: transform 0.22s ease, opacity 0.22s ease;
}
