@font-face{font-family:'Inter';src:url('assets/inter-400.woff2') format('woff2');font-weight:400;font-style:normal;font-display:swap;}
@font-face{font-family:'Inter';src:url('assets/inter-500.woff2') format('woff2');font-weight:500;font-style:normal;font-display:swap;}
@font-face{font-family:'IBM Plex Mono';src:url('assets/ibm-plex-mono-400.woff2') format('woff2');font-weight:400;font-style:normal;font-display:swap;}
@font-face{font-family:'IBM Plex Mono';src:url('assets/ibm-plex-mono-500.woff2') format('woff2');font-weight:500;font-style:normal;font-display:swap;}
@font-face{font-family:'Ballinger XCond';src:url('assets/ballinger-xcond-xbold.woff2') format('woff2');font-weight:800;font-style:normal;font-display:block;}
  :root {
    --bt-black: #000000;
    --bt-white: #FFFFFF;
    --bt-grey: #8E8E8E;
    --bt-grey-dark: #2A2A2A;
    /* Decoration vs. touchable, at a glance: --bt-grey-dark hairlines are
       never interactive, --bt-grey-mid is the one grey used on an
       interactive boundary at rest (the gate's input underlines), a full
       step up so it clears WCAG 1.4.11's 3:1 for UI components (3.24:1). */
    --bt-grey-mid: #5E5E5E;
    --bt-dim: #757575;
    /* Everything else on the page is sharp-cornered, per the design system.
       The card is the deliberate exception, a standalone object, not the seed
       of a new shape language the rest of the UI should pick up. */
    --bt-surface: #141414;
    /* --bt-dim measures 4.56:1 against pure black, which just clears AA. On
       the card's slightly lighter --bt-surface that same value drops below
       AA. This is the one grey tuned for --bt-surface instead: 4.66:1 there. */
    --bt-dim-card: #808080;
    --bt-mirror: #E2544A;
    --bt-field: #6BD96E;
    --bt-room: #EFA94C;
    --bt-future: #4C6EF5;
    /* Module names only. Embedded as a subset data URI by build.mjs. */
    --module: 'Ballinger XCond', 'Oswald', 'Archivo Narrow', 'Haettenschweiler', sans-serif;
    --mono: 'IBM Plex Mono', 'Roboto Mono', monospace;
    --body: 'Helvetica Neue', 'Inter', Helvetica, Arial, sans-serif;
  }

  * { margin: 0; padding: 0; box-sizing: border-box; }
  html, body { background: var(--bt-black); }
  body {
    font-family: var(--body);
    color: var(--bt-white);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
  }
  button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; text-align: left; }
  button, .screen, a.blockbtn, #talk-link {
    -webkit-user-select: none; user-select: none;
    -webkit-tap-highlight-color: transparent;
    touch-action: manipulation;
  }
  button:focus-visible, a:focus-visible, input:focus-visible {
    outline: 2px solid var(--bt-white);
    outline-offset: 4px;
  }
  /* Headings are focused programmatically to carry the reading position across
     screens. They are not interactive, so they take no ring. */
  .stem:focus, h1:focus { outline: none; }
  a { color: inherit; }

  .mono {
    font-family: var(--mono);
    font-size: 11px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
  }

  /* ---- chrome bar (the landing page's, ported) ----
     The client asked for the landing page's frosted nav bar here so the two
     products read as one. This DELIBERATELY overrides the Bible's section 13
     banned list (border radius, drop shadow, gradient highlight, pill shape)
     for this one element - a client decision on 15 Sep 2026, not drift.
     Everything else on the page keeps the sharp-cornered, shadowless system.
     Values are the landing page's .chrome / .glass / .btn rules, dark theme
     only (the funnel has no light sections, so none of the paper-theme or
     chrome-on-light logic comes across). It sits under #grain (z 50) so the
     grain texture covers it like everything else. */
  .chrome {
    position: fixed;
    top: max(4px, env(safe-area-inset-top));
    left: max(7px, env(safe-area-inset-left));
    right: max(7px, env(safe-area-inset-right));
    z-index: 40;
    display: flex; align-items: center; justify-content: space-between;
    height: 50px; padding: 0 12px;
    isolation: isolate; overflow: hidden;
    background: rgba(255, 255, 255, .04);
    -webkit-backdrop-filter: blur(16px) saturate(180%);
    backdrop-filter: blur(16px) saturate(180%);
    border: 1px solid rgba(255, 255, 255, .16);
    border-radius: 14px;
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, .22),
      inset 0 -1px 0 rgba(255, 255, 255, .05),
      0 18px 44px rgba(0, 0, 0, .28);
  }
  /* The soft highlight catching the top-left edge - what reads as glass
     rather than a blurred panel. Behind the content, in front of the blur. */
  .chrome::before {
    content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
    background:
      radial-gradient(circle at 18% 0%, rgba(255, 255, 255, .30), transparent 38%),
      linear-gradient(115deg, rgba(255, 255, 255, .14), transparent 46%);
  }
  @media (prefers-reduced-transparency: reduce) {
    .chrome { -webkit-backdrop-filter: none; backdrop-filter: none; background: var(--bt-black); }
    .chrome::before { display: none; }
  }
  .chrome .mark { display: flex; align-items: center; }
  .wordmark-logo { height: 15px; width: auto; display: block; flex: 0 0 auto; }
  .chrome-actions { display: flex; align-items: center; gap: 10px; margin-right: -8px; }
  /* min-height keeps the tap target honest while the visual stays compact;
     700 because Helvetica's 400 reads thin at this size. */
  .btn {
    display: inline-flex; align-items: center; justify-content: center;
    /* Same scale as the landing page's chrome bar (see there): 11px in a
       34px pill on desktop, 12px in 35px on phones. */
    min-height: 34px; padding: 8px 14px;
    font-family: var(--body); font-size: 11px; font-weight: 700;
    text-transform: uppercase; letter-spacing: .04em; text-decoration: none;
    border: 0; border-radius: 4px; cursor: pointer; white-space: nowrap;
    transition: background-color .2s ease, color .2s ease, border-color .2s ease;
  }
  .btn-primary { background: #3D3B36; color: #F5F4F2; border: 1px solid rgba(255, 255, 255, .12); }
  .btn-primary:hover { background: #F1393A; border-color: transparent; }
  .chrome-quiet { background: none; border: 1px solid rgba(255, 255, 255, .5); color: var(--bt-white); }
  .chrome-quiet:hover { border-color: #F1393A; color: #F1393A; }
  .chrome-short { display: none; }
  @media (max-width: 640px) {
    .chrome .btn { padding: 9px 12px; font-size: 12px; }
    .chrome-long { display: none; }
    .chrome-short { display: inline; }
  }
  #back {
    position: fixed; bottom: 0; left: 0; z-index: 40;
    padding: 16px 28px calc(16px + env(safe-area-inset-bottom, 8px));
    /* Handover specifies 40 percent, which measures 3.66:1 and fails AA on the
       only control that can correct an answer. 55 percent is the nearest value
       that passes. Revert to 0.4 if the design would rather take the hit. */
    opacity: 0.55; color: var(--bt-white); display: none;
  }
  #back:hover, #back:focus-visible { opacity: 1; }

  #rule {
    position: fixed; bottom: 0; left: 0; height: 2px; width: 100%;
    background: var(--bt-grey); z-index: 30;
    transform: scaleX(0); transform-origin: left;
    transition: transform 0.4s ease, background 0.4s ease, opacity 0.3s ease;
    opacity: 0;
  }
  #sweep {
    position: fixed; bottom: 0; left: 0; height: 2px; width: 100%;
    background: rgba(255, 255, 255, 0.75); z-index: 31;
    transform: scaleX(0); transform-origin: left;
  }

  #grain {
    position: fixed; top: 0; right: 0; bottom: 0; left: 0; inset: 0;
    z-index: 50; pointer-events: none; opacity: 0.04;
  }

  /* ---- funnel stage ---- */
  #stage {
    position: fixed; top: 0; right: 0; bottom: 0; left: 0; inset: 0;
    overflow: hidden;
    height: 100vh;
    height: 100dvh;
  }
  .screen {
    position: absolute; top: 0; right: 0; bottom: 0; left: 0; inset: 0;
    display: flex; flex-direction: column;
    overflow-y: auto; /* short viewports scroll instead of clipping */
    -webkit-overflow-scrolling: touch;
    padding: 76px 24px calc(72px + env(safe-area-inset-bottom, 0px));
  }
  /* margin auto rather than justify-content: center, which clips overflow at the top */
  .screen > .inner { width: 100%; max-width: 680px; margin: auto; }

  /* Question counter. Names the position, never the module. Text stays white
     or grey throughout; colour is reserved for UI elements (bars, squares,
     the hover/selected hairline), never for copy. */
  .qcount {
    font-family: var(--mono); font-size: 11px; letter-spacing: 0.18em;
    color: var(--bt-dim); margin-bottom: 26px;
    font-variant-numeric: tabular-nums;
  }
  .qcount b {
    display: inline-block;
    color: var(--bt-white); font-weight: 400;
    animation: qcount-tick 0.2s cubic-bezier(0.2, 0.7, 0.2, 1) 200ms backwards;
  }
  @keyframes qcount-tick {
    from { transform: translateY(-6px); opacity: 0; }
    to { transform: translateY(0); opacity: 1; }
  }

  .stem {
    font-size: clamp(23px, 3.1vw, 31px);
    font-weight: 400; line-height: 1.28; letter-spacing: -0.018em;
    max-width: 600px; margin-bottom: 44px; text-wrap: balance;
  }

  .options { border-bottom: 1px solid var(--bt-grey-dark); }
  .opt {
    display: flex; align-items: center; gap: 18px;
    width: 100%; min-height: 58px; padding: 16px 12px 16px 0;
    border-top: 1px solid var(--bt-grey-dark);
    position: relative;
    transition: padding-left 0.18s cubic-bezier(0.2, 0.7, 0.2, 1);
    /* Cascades in after the stem, question first then choices: the single
       change that does the most to break the "form template" feel. */
    animation: opt-in 0.32s cubic-bezier(0.2, 0.7, 0.2, 1) backwards;
    animation-delay: calc(200ms + var(--i, 0) * 45ms);
  }
  @keyframes opt-in {
    from { opacity: 0; transform: translateY(6px); }
    to { opacity: 1; transform: translateY(0); }
  }
  /* A hairline in the module colour rides in from the left on hover and stays
     put once chosen, so the active row is legible without relying on colour. */
  .opt::before {
    content: ''; position: absolute; left: 0; top: 0; bottom: 0;
    width: 2px; background: var(--accent, var(--bt-grey));
    transform: scaleY(0); transform-origin: center;
    transition: transform 0.18s cubic-bezier(0.2, 0.7, 0.2, 1);
  }
  .opt:hover, .opt[aria-checked="true"] { padding-left: 14px; }
  .opt:hover::before, .opt[aria-checked="true"]::before { transform: scaleY(1); }
  /* Mirrors the 700ms auto-advance countdown on the selected row itself, in
     the accent colour, instead of leaving it to a 2px line 160-393px away at
     the extreme bottom of the viewport: this is where the eye already is. */
  .opt::after {
    content: ''; position: absolute; left: 0; right: 0; bottom: 0;
    height: 2px; background: var(--accent, var(--bt-grey));
    transform: scaleX(0); transform-origin: left;
  }
  /* Transition lives only on .arming: removing the class snaps the bar back
     with no reverse-playing animation, so a correction click resets cleanly. */
  .opt.arming::after { transform: scaleX(1); transition: transform 700ms linear; }
  .opt-marker {
    display: flex; align-items: center; gap: 10px;
    flex: 0 0 auto;
    margin-right: 18px; /* gap fallback, zeroed below where gap is supported */
  }
  @supports (gap: 1px) {
    .opt-marker { margin-right: 0; }
  }
  /* Signal-bars mark, ascending A to D: a quiet, always-on hint that this
     row sits at one of four positions on a scale. Its own grey scale, never
     the module accent, so it never competes with the hover/selected state. */
  .opt .spectrum { display: flex; align-items: flex-end; gap: 2px; }
  .opt .spectrum-bar {
    width: 2px; background: var(--bt-grey-dark);
    transition: background 0.15s ease;
  }
  .opt .spectrum-bar:nth-child(1) { height: 4px; }
  .opt .spectrum-bar:nth-child(2) { height: 7px; }
  .opt .spectrum-bar:nth-child(3) { height: 10px; }
  .opt .spectrum-bar:nth-child(4) { height: 13px; }
  .opt .spectrum-bar.lit { background: var(--bt-grey); }
  .opt .sq {
    flex: 0 0 auto; width: 12px; height: 12px;
    border: 1px solid var(--accent, var(--bt-grey));
    transition: background 0.15s ease, transform 0.15s ease;
  }
  .opt .txt {
    font-size: 17px; line-height: 1.4; color: var(--bt-grey);
    transition: color 0.2s ease;
  }
  .opt:hover .txt { color: #C9C9C9; }
  .opt:hover .sq { transform: scale(1.15); }
  /* The tap landed: a one-off confirmation, not a hover state, so it is its
     own animation rather than reusing the hover transform. */
  .opt .sq.pulse { animation: sq-pulse 0.18s ease-out; }
  @keyframes sq-pulse {
    0% { transform: scale(1); }
    50% { transform: scale(1.15); }
    100% { transform: scale(1); }
  }
  .opt[aria-checked="true"] .txt { color: var(--bt-white); }
  .opt[aria-checked="true"] .sq { background: var(--accent, var(--bt-grey)); }
  /* These stay clickable for 700ms after a choice, and permanently on any screen
     reached via Back, so they cannot use --bt-highlight (2.37:1). --bt-dim is
     the darkest grey that still passes AA against black. */
  .options.locked .opt:not([aria-checked="true"]) .txt { color: var(--bt-dim); }

  /* ---- intro ---- */
  /* The front door gets the same grammar every later screen already uses:
     a mono eyebrow, and here, a quiet preview of the reveal's colour system
     (the card's own bar order) before a single question has been asked. */
  .intro .eyebrow { margin-bottom: 18px; }
  .intro-marks { display: flex; gap: 6px; margin-bottom: 28px; }
  .intro-marks span { width: 22px; height: 2px; }
  .intro-marks span:nth-child(1) { background: var(--bt-mirror); }
  .intro-marks span:nth-child(2) { background: var(--bt-field); }
  .intro-marks span:nth-child(3) { background: var(--bt-room); }
  .intro-marks span:nth-child(4) { background: var(--bt-future); }
  /* Cascades in with the same rhythm as the option rows and case notes below,
     so the funnel's front door and its close share one grammar. */
  .intro .eyebrow, .intro-marks, .intro h1, .intro p, .textlink {
    animation: opt-in 0.4s cubic-bezier(0.2, 0.7, 0.2, 1) backwards;
    animation-delay: calc(80ms + var(--i, 0) * 90ms);
  }
  /* Begin was previously the quietest control on the page - a 13px
     underlined link, the same weight as every other textlink on the site -
     on the one screen whose entire job is to get a click. Same charcoal-
     at-rest, red-on-hover recipe as .blockbtn (see there) rather than a
     third button language; stays a .textlink underneath (same class, same
     click handler, same reduced-motion carve-out) so nothing about its
     behaviour changes, only its weight. */
  /* .textlink.intro-cta, not .intro-cta alone: the base .textlink rule
     (color: var(--bt-white)) is defined later in the file and would
     otherwise win on source order despite equal specificity -- confirmed
     live, the button rendered with the right background but the wrong
     text colour until this was scoped to both classes. */
  .textlink.intro-cta {
    display: inline-flex; align-items: center; justify-content: center;
    background: #3D3B36; color: #F5F4F2;
    border: 1px solid rgba(255, 255, 255, .12);
    padding: 16px 40px; margin-top: 4px;
    transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease, transform 0.1s ease;
  }
  .intro-cta::after { display: none; } /* the underline-reveal is for a bare link, not a filled block */
  /* Same swap as .blockbtn:hover, same reason: white-on-red here measured
     3.55:1, fails AA. */
  .intro-cta:hover { background: #F1393A; border-color: transparent; color: #000000; }
  .intro-cta:active { transform: translateY(1px); }
  .intro h1 {
    font-size: clamp(34px, 5.4vw, 52px);
    font-weight: 500; line-height: 1.12; letter-spacing: -0.02em;
    margin-bottom: 24px; text-wrap: balance;
  }
  .intro p {
    font-size: 16px; line-height: 1.6; color: var(--bt-grey);
    max-width: 460px; margin-bottom: 36px;
  }
  /* inline-block plus real vertical padding so the tap target clears 24px.
     The underline slides in on hover/focus rather than sitting there always,
     the same hairline-reveal language as the option rows below, so the first
     interactive element on the page teaches its interaction grammar. Touch
     has no hover to reveal it, so it stays permanently on there instead. */
  .textlink {
    display: inline-block; position: relative;
    font-family: var(--mono); font-size: 13px; letter-spacing: 0.18em;
    text-transform: uppercase; color: var(--bt-white);
    padding: 16px 0 12px;
    transition: transform 0.1s ease;
  }
  .textlink::after {
    content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
    background: var(--bt-white);
    transform: scaleX(0); transform-origin: left;
    transition: transform 0.18s cubic-bezier(0.2, 0.7, 0.2, 1);
  }
  .textlink:hover::after, .textlink:focus-visible::after { transform: scaleX(1); }
  @media (hover: none) {
    .textlink::after { transform: scaleX(1); }
  }

  /* ---- role ---- */
  .rolelabel {
    font-family: var(--mono); font-size: 11px; letter-spacing: 0.18em;
    text-transform: uppercase; color: var(--bt-grey); margin-bottom: 28px;
  }
  /* This screen used to read as question zero of ten, with no indication it
     is not scored and not part of the diagnosis. role-stem tightens .stem's
     usual 44px margin so the reason line sits close under the question it
     is explaining, rather than floating in that gap. */
  .role-stem { margin-bottom: 8px; }
  .role-reason { font-size: 14px; color: var(--bt-grey); max-width: 440px; margin-bottom: 36px; }

  /* ---- compiling beat ----
     Honest theatre between the last answer and the reveal: the engine has
     already run by the time this is on screen, the pause is presentation
     only. A screen reader hears one static line once (.sr-only); sighted
     users see the three lines cross in the visible, aria-hidden pair. */
  .sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
  }
  .screen.compiling .inner { text-align: center; }
  .compiling-lines { position: relative; height: 24px; }
  .compiling-line {
    position: absolute; top: 0; left: 0; right: 0;
    font-family: var(--mono); font-size: 13px; letter-spacing: 0.1em;
    text-transform: uppercase; color: var(--bt-grey); text-align: center;
    opacity: 0; transform: translateY(12px);
    transition: transform 0.25s ease-out, opacity 0.25s ease-out;
  }
  .compiling-line.active { opacity: 1; transform: translateY(0); }
  .compiling-line.exited { opacity: 0; transform: translateY(-12px); }
  .compiling-marks { display: flex; gap: 6px; justify-content: center; margin-bottom: 22px; }
  .compiling-marks span { width: 22px; height: 2px; animation: compiling-pulse 1.6s ease-in-out infinite; }
  .compiling-marks span:nth-child(1) { background: var(--bt-mirror); animation-delay: 0s; }
  .compiling-marks span:nth-child(2) { background: var(--bt-field); animation-delay: 0.2s; }
  .compiling-marks span:nth-child(3) { background: var(--bt-room); animation-delay: 0.4s; }
  .compiling-marks span:nth-child(4) { background: var(--bt-future); animation-delay: 0.6s; }
  @keyframes compiling-pulse {
    0%, 100% { opacity: 0.35; }
    50% { opacity: 1; }
  }
  @media (prefers-reduced-motion: reduce) {
    .compiling-line { transition: none; }
    .compiling-marks span { animation: none; opacity: 0.7; }
  }

  /* ---- results layout ----
     Card on top, CTA below it, on every screen up to 1000px: that is most
     phones AND most tablets in portrait, so "mobile" here means "narrower
     than a comfortable two-column reading width," not a literal phone check.
     Above that, two columns: the card left, the conversion column right, at a
     fixed width so surplus space goes to the card rather than stretching the
     sidebar's short lines of text. */
  .results-top {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 56px;
    align-items: start;
    margin-bottom: 72px;
  }
  /* justify-self stays at its default (stretch), so .dcard always gets a
     definite width from its grid column: the card's own content now lives in
     absolutely-positioned faces (for the flip) and can no longer hand a
     size back up the tree the way in-flow content used to. Starts invisible:
     the entrance choreography (script) brings it in, so there is never a
     flash of the card at rest. No frame around it: the card stands on its
     own against the page black, its own hairline border and keyline is
     enough. */
  .dcard {
    perspective: 1400px;
    opacity: 0;
  }
  @media (min-width: 1000px) {
    .results-top {
      /* Fixed, not minmax(0, 440px): between 1000-1090px viewports a minmax
         upper bound would let the card shrink below the width its type scale
         is tuned to. justify-content centres the pair when the viewport
         hands back more room than the two fixed/capped tracks want. */
      grid-template-columns: 440px minmax(0, 560px);
      justify-content: center;
      gap: clamp(32px, 4.5vw, 64px);
      margin-bottom: 96px;
    }
    /* Framed and full height, matching the card's own housing, so the
       conversion column reads as anchored beside it rather than floating
       text in open black. The entrance opacity/transform live on the base
       #gate rule below now (they used to live only here, so the mobile
       conversion moment was bare text visible from t=0 -- this just
       upgrades the mobile hairline to a full frame). */
    #gate {
      border: 1px solid var(--bt-grey-dark);
      padding: clamp(28px, 3vw, 44px);
      /* NOT stretch: that matched the box's height to the card beside it
         (the card is much taller, driven by the diagnosis name), and
         margin-top:auto on the form below pushed the fields to the bottom
         of that borrowed height -- a void of about 94px between the
         microcopy and "First name" that belonged to nothing. align-self:
         start lets the box size to its own content instead. */
      align-self: start;
      display: flex;
      flex-direction: column;
    }
    #gate-form { margin-top: 32px; }
    /* Once the gate is gone, the lone card recentres over the notes column
       below rather than staying pinned hard left beside empty black. */
    .results-top.gated { grid-template-columns: minmax(0, 440px); justify-content: center; }
  }

  /* ---- diagnosis card ----
     A pseudo-3D object, three nested layers, each owning exactly one
     transform so they never fight:
       .dcard       sets the perspective, static
       .dcard-tilt  pointer parallax, rotateX/rotateY, small angles
       .dcard-flip  the click-to-flip, rotateY 0 or 180deg
     Locked to 5:7, the proportions of a real trading card (2.5in x 3.5in):
     the card can change SIZE but never SHAPE. No internal scroll: content is
     sized to fit the box, and a preview-build console check (see script)
     warns if it ever does not. Depth comes from the tilt, the flip and the
     edge, never a gradient or drop shadow. */
  .dcard-tilt {
    position: relative;
    width: 100%;
    max-width: 440px;
    margin: 0 auto;
    transform-style: preserve-3d;
    transition: transform 0.45s cubic-bezier(0.2, 0.7, 0.2, 1);
    will-change: transform;
    /* Lets .dcard-flip query the card's own rendered width below, rather
       than the viewport: max-width caps this element before the viewport
       and the card width necessarily match. */
    container-type: inline-size;
  }
  .dcard-flip {
    position: relative;
    z-index: 1;
    width: 100%;
    aspect-ratio: 5 / 7;
    transform-style: preserve-3d;
    transition: transform 0.7s cubic-bezier(0.4, 0.02, 0.2, 1);
    cursor: pointer;
  }
  /* Below this the card itself is rendering under 400px wide: measured
     against all ten diagnoses, 5:7 does not hold the longest names and
     tension lines even after the spacing pass below. Bridge-card proportions
     (the "a little taller" fallback) buy the room back without abandoning
     the trading-card read. */
  @container (max-width: 400px) {
    .dcard-flip { aspect-ratio: 0.64; }
  }
  .dcard.is-flipped .dcard-flip { transform: rotateY(180deg); }
  /* An unprompted nudge before the first flip: the card eases into a partial
     turn and back, on its own, a few times, so the object reads as something
     that spins rather than something that happens to have two sides. Plays
     only before the first real flip (armIdleWobble stops scheduling once
     has-flipped is set) and only while nothing is actively driving the tilt
     layer by hand (see the "hovering" guard in setupCard). A CSS animation
     rather than the inline-transform technique the entrance settle uses,
     since this one repeats and animationend gives a clean, jitter-free way
     to know when to remove the class. */
  @keyframes dcard-idle-wobble {
    0%, 100% { transform: none; }
    38% { transform: rotateY(-13deg) translateZ(3px); }
    68% { transform: rotateY(4deg); }
  }
  .dcard-flip.idle-wobble {
    animation: dcard-idle-wobble 1.7s cubic-bezier(0.4, 0.02, 0.2, 1) 1;
  }
  /* Real edge thickness. Without this, the flip and the idle wobble both
     carry the card through (near) 90deg, where two zero-thickness faces
     vanish to nothing -- the card's two signature motions were proving it
     paper-thin. Two strips of the card's own core colour, perpendicular to
     the faces, edge-on and invisible at rest, catch the light as the card
     turns: this is "the edge" the system's own depth-sources comment
     already names, made literal rather than assumed. */
  .dcard-flip::before, .dcard-flip::after {
    content: ''; position: absolute; top: 8px; bottom: 8px; width: 3px;
    background: #2E2E2E; border-radius: 1px;
    pointer-events: none;
  }
  .dcard-flip::before { left: 0; transform-origin: left center; transform: rotateY(90deg); }
  .dcard-flip::after { right: 0; transform-origin: right center; transform: rotateY(-90deg); }
  .dcard-face {
    position: absolute; inset: 0;
    display: flex;
    flex-direction: column;
    /* Top-anchored (was centred): centring split the card's spare room into
       two dead pools - measured 151px above the eyebrow and 124px below
       the definition at 440px wide, 192/165 on a short name. Content now
       stacks from the top and the spare room sits between the copy and a
       foot row (.dcard-foot, margin-top:auto) that gives the bottom edge
       something to hold: the module marks and the hint on the front, the
       hint on the back. */
    justify-content: flex-start;
    /* The card's one rhythm unit: every gap that separates one typographic
       register from another (label from headline, name from definition, the
       back's own divider) is a named multiple of this, so the air between
       elements comes from one system instead of independently tuned numbers.
       Scaled by cqw like the card's own padding, so the rhythm opens and
       tightens with the card rather than the viewport. Deliberately NOT used
       inside the module-row readout on the back: that block's own row-to-row
       spacing stays a fixed constant instead (see .barrow), since a specimen
       readout's density should read as a property of the instrument, not
       something that visibly loosens just because the card got bigger.
       Raised from clamp(5px,1.6cqw,8px): the same system, just given more
       air throughout -- every gap below is still the same named multiple
       of this one unit, so the rhythm stays intact, just less tight. */
    --u: clamp(6px, 2cqw, 10px);
    /* Card-stock grain, in object space: a fine white-only tooth that tilts
       and flips WITH the card, unlike the page's own fixed #grain overlay,
       which sits in screen space and never moves with what it's covering --
       "grain on the camera, not on the card." Alpha-only (the colour matrix
       pins every pixel to pure white and only ever varies its opacity), so
       it can lighten the surface but never tint it. A different turbulence
       frequency from the page grain's 0.8, so the two never interference-
       pattern where they overlap. */
    background-color: var(--bt-surface);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.1' numOctaves='1' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.05 0'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E");
    background-size: 120px 120px;
    /* A lit top edge, a shaded bottom: the uniform outline (the card's only
       cue for standing off the ground, since a shadow is invisible on pure
       black) becomes a rim catching ambient light instead of a drawn box. */
    border: 1px solid var(--bt-grey-dark);
    border-top-color: #383838;
    border-bottom-color: #232323;
    border-radius: 14px;
    /* Bottom padding reserves real room for the "Tap to spin" hint, which is
       absolutely positioned so it never affects the overflow calculation
       below, but still needs the flex content to stop short of it. Keyed to
       cqw (the card's own width, via the container on .dcard-tilt) rather
       than vw: viewport-relative padding was hitting its maximum on ordinary
       desktop windows while the card itself is capped at 440px, which is
       what let the card's last line print through the hint below. */
    padding: clamp(20px, 5cqw, 32px) clamp(15px, 4cqw, 26px) clamp(18px, 4.5cqw, 26px);
    overflow: hidden;
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;
    transition: border-color 0.35s ease;
  }
  .dcard:hover .dcard-face { border-color: #3D3D3D; border-top-color: #4C4C4C; border-bottom-color: #333333; }
  .dcard-face.back { transform: rotateY(180deg); }
  /* The edge light. The previous version was a conic gradient on a layer
     BEHIND the card that relied on the card's own opaque face to hide the
     gradient's centre - so any tilt, wobble or flip, anything that took the
     face off flat, exposed that centre as a sweep across the middle of the
     card. This is the standard masked-ring technique instead: the layer IS
     a 1.5px ring (mask-composite: exclude cuts the content box out of the
     border box), so it cannot show as a disc at any angle. One per face,
     inside the face, so it turns with the card. Two layers: a static rim
     lit from above, and a radial highlight that follows the pointer
     (--lx/--ly from the tilt's own mousemove; the back face reads the
     mirrored x, --lxm, because it is rotated 180deg), faded in by --lit
     while the pointer is on the card. Touch and reduced motion get the
     static rim only. */
  .dcard-face::after {
    content: ''; position: absolute; inset: 0; border-radius: 14px;
    padding: 1.5px; pointer-events: none;
    background:
      radial-gradient(240px circle at var(--lx, 50%) var(--ly, 50%), rgba(255, 255, 255, calc(0.6 * var(--lit, 0))), rgba(255, 255, 255, 0) 60%),
      linear-gradient(180deg, rgba(255, 255, 255, 0.22), rgba(255, 255, 255, 0.05) 42%, rgba(255, 255, 255, 0) 75%);
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    mask-composite: exclude;
    transition: --lit 0.35s ease;
  }
  .dcard-face.back::after {
    background:
      radial-gradient(240px circle at var(--lxm, 50%) var(--ly, 50%), rgba(255, 255, 255, calc(0.6 * var(--lit, 0))), rgba(255, 255, 255, 0) 60%),
      linear-gradient(180deg, rgba(255, 255, 255, 0.22), rgba(255, 255, 255, 0.05) 42%, rgba(255, 255, 255, 0) 75%);
  }
  @property --lit { syntax: '<number>'; inherits: true; initial-value: 0; }
  @media (hover: hover) and (prefers-reduced-motion: no-preference) {
    .dcard-tilt:hover { --lit: 1; }
  }

  /* The foot: in flow at the bottom of the face, not an absolutely
     positioned hint floating in whatever space was left. */
  .dcard-foot {
    margin-top: auto; padding-top: calc(var(--u) * 2);
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    flex: 0 0 auto;
  }
  .dcard-hint {
    color: var(--bt-dim-card); margin-left: auto; white-space: nowrap;
    transition: opacity 0.3s ease;
  }
  /* The three scored modules as marks, the intro's own 22x2px ticks: a
     quiet preview of the evidence on the back, in the same colours the
     name is inked in. The two weakest (the name's pair) sit at full
     strength, the strongest steps back. */
  .dcard-marks { display: flex; gap: 6px; }
  .dcard-marks span { width: 22px; height: 2px; background: var(--mk, var(--bt-grey)); opacity: var(--mk-op, 1); }
  .dcard.has-flipped .dcard-hint { opacity: 0; pointer-events: none; }


  /* Pointer tilt: nothing on touch, nothing under reduced motion. The flip
     still works either way, it is just an instant swap under reduced motion. */
  @media (hover: none), (prefers-reduced-motion: reduce) {
    .dcard-tilt { transform: none !important; transition: none; }
  }
  @media (prefers-reduced-motion: reduce) {
    .dcard-flip { transition: none; }
    .dcard-flip.idle-wobble { animation: none; }
  }

  /* Sections inside the card: verdict, then the readout. The divider is the
     one unmistakable seam on the back, 3 rhythm units, deliberately smaller
     than the front's 4-unit name gap: the back carries five elements to the
     front's three and cannot spend as generously on any single pause. */
  .dcard-divider {
    height: 1px; background: var(--bt-grey-dark);
    margin: calc(var(--u) * 3) 0;
  }
  /* Same 1.5-unit kicker gap as the front eyebrow, so both faces open with
     the same beat instead of two independently tuned first gaps. */
  .dcard-label {
    font-family: var(--mono); font-size: 11px; line-height: 1; letter-spacing: 0.18em;
    text-transform: uppercase; color: var(--bt-dim-card); margin-bottom: calc(var(--u) * 1.5);
  }

  /* ---- results ---- */
  #results { display: none; }
  #results.active { display: block; }
  #results {
    min-height: 100vh; min-height: 100dvh;
    padding: 110px 24px 90px;
  }
  /* Wide enough for the two-column top. Reading sections below stay narrow. */
  #results .inner { max-width: 1180px; margin: 0 auto; }

  .eyebrow {
    font-family: var(--mono); font-size: 11px; letter-spacing: 0.18em;
    text-transform: uppercase; color: var(--bt-grey); margin-bottom: 12px;
  }
  /* A kicker hugs the headline it announces rather than floating evenly
     between neighbours: 1.5 rhythm units, tight enough to read as a caption
     glued to the name, not a peer of it. The back's .dcard-label uses this
     exact same value, so both faces open with the same beat. */
  .dcard-face .eyebrow { line-height: 1; margin-bottom: calc(var(--u) * 1.5); }

  /* The diagnosis name is body sans, not Ballinger: the module names below
     keep the display face, this is the one place set in the same voice as
     the rest of the card's copy, just at headline weight and size. Sized off
     the card's own width (cqw, via the container on .dcard-tilt) rather than
     the viewport, so it cannot outgrow a card that has stopped growing. Grown
     substantially once the tension/module content moved to the back: the
     front now exists to carry this one line, so it gets to be the largest
     thing the card has ever shown. */
  #cond-name {
    font-family: var(--body);
    font-weight: 500;
    font-size: clamp(30px, 13cqw, 62px);
    /* Tightened from 1.06/-0.015em, standard practice at display sizes: the
       small height it saves is spent below, not on growing the size again.
       This is the one generous pause the front is allowed, 4 rhythm units,
       the most air spent on any single gap on the card. */
    line-height: 1.03; letter-spacing: -0.02em;
    margin-bottom: calc(var(--u) * 4);
    overflow-wrap: break-word; text-wrap: balance;
    /* A soft sheen, not a colour: a near-white band drifts diagonally through
       an otherwise even light grey, the way light catches foil or brushed
       metal rather than a coloured verdict. (An earlier version inked the
       name in the two strongest-scoring modules; some pairings read as
       genuinely bad together, e.g. red against amber, and which pairing a
       visitor got was luck, not a choice either of us made -- so the colour
       coding is gone and this reads the same, softly, on every diagnosis.)
       This is the second and last deliberate exception to the no-gradient
       rule, alongside the edge light. */
    /* Base lifted from #D8D8D8 (216) to #F0F0F0 (240): at the old value the
       biggest type on the card was reading as flat grey UNDER #cond-def's
       plain white body copy right beneath it -- the diagnosis inverted its
       own hierarchy. #FFFFFF at the peak keeps a sheen at all, just a
       subtler one, closer to how a near-white surface actually catches
       light than the wider swing did. */
    /* The two colours are the two weakest-scoring modules for this result
       (--ink-a, --ink-b, set by showResults; tie-break Mirror, Field,
       Room). Each is a soft radial field on its own half of the name that
       fades to nothing well before the other's centre, so the pair meets
       only faintly, through the off-white base, rather than mixing head-on
       - the pairing that made an earlier two-colour version look bad (red
       against amber) never gets to happen at full strength. The fields
       drift on two different slow loops (registered custom properties, so
       the positions interpolate), which is what makes it liquid rather
       than a static wash. */
    background-image:
      radial-gradient(42% 120% at var(--ga-x) var(--ga-y), var(--ink-a, #F0F0F0) 0%, rgba(240, 240, 240, 0) 100%),
      radial-gradient(42% 120% at var(--gb-x) var(--gb-y), var(--ink-b, #F0F0F0) 0%, rgba(240, 240, 240, 0) 100%),
      linear-gradient(#F0F0F0, #F0F0F0);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent;
    animation: cond-name-a 9s ease-in-out infinite, cond-name-b 13s ease-in-out infinite;
  }
  @property --ga-x { syntax: '<percentage>'; inherits: false; initial-value: 16%; }
  @property --ga-y { syntax: '<percentage>'; inherits: false; initial-value: 30%; }
  @property --gb-x { syntax: '<percentage>'; inherits: false; initial-value: 84%; }
  @property --gb-y { syntax: '<percentage>'; inherits: false; initial-value: 70%; }
  @keyframes cond-name-a {
    0%, 100% { --ga-x: 12%; --ga-y: 25%; }
    50% { --ga-x: 26%; --ga-y: 75%; }
  }
  @keyframes cond-name-b {
    0%, 100% { --gb-x: 88%; --gb-y: 70%; }
    50% { --gb-x: 74%; --gb-y: 25%; }
  }
  #cond-def {
    /* Was vw: the card is capped in size before the viewport is, so a
       viewport-relative size drifts out of step with a box that stopped
       growing. cqw tracks the card itself, like everything else on it. */
    font-size: clamp(13.5px, 3.5cqw, 15.5px); line-height: 1.5;
    max-width: 56ch; margin-bottom: 0;
  }
  /* Was mono uppercase, which is a label register and made a full sentence hard
     to read. Body type with a mono label above it keeps one register per job.
     This is the back's dominant statement, the equivalent of the front's
     hero name, so it is the one sentence on the card set at 500 weight
     rather than the plain text weight everything else reads at. Was vw,
     fixed to cqw for the same reason as #cond-def above. Held to a 16px
     ceiling rather than a hair higher: this is the single riskiest line on
     the card, its longest real example runs two full sentences, and it
     needs genuine spare margin against the box, not a value that only just
     fits it. */
  #tension-line {
    font-size: clamp(14px, 3.75cqw, 16px); font-weight: 500; line-height: 1.4;
    letter-spacing: -0.01em; color: var(--bt-white);
    max-width: 54ch;
  }

  /* Four rows in a fixed-height card need to read as data, not as four more
     small headlines, so the module name here is closer to a large label than
     to display type. Deliberately off the --u scale: a specimen readout's
     row density is a constant of the instrument, not something that should
     visibly loosen just because the card grew. Row-to-row (8px) is exactly
     double the subtitle's own gap (4px) below it, a stated 2:1 ratio rather
     than two numbers tuned independently of each other. */
  .barrow { max-width: 680px; margin-bottom: 8px; }
  .barrow .rowtop {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: 16px; margin-bottom: 2px;
  }
  .barrow .subt {
    display: block; font-size: 12px; line-height: 1.3; color: var(--bt-grey); margin-bottom: 4px;
  }
  /* Ceiling pulled back from 20px to 19px, deliberately, now that the tension
     line above it (on the back) is set bigger and heavier: this is a data-row
     label, not a second headline, and needs to sit clearly below the tension
     line in authority. Line-height tightened too: the condensed face's
     default leading ran generous at this size. */
  .barrow .modname {
    font-family: var(--module);
    font-size: clamp(16px, 4.3cqw, 19px);
    line-height: 0.92; letter-spacing: 0.01em;
    text-transform: uppercase; color: var(--bt-white);
    flex: 1 1 auto; min-width: 0;
  }
  .barrow .bandword {
    font-family: var(--mono); font-size: 11px; letter-spacing: 0.18em; font-weight: 500;
    text-transform: uppercase; color: var(--bt-white); flex: 0 0 auto;
  }
  .barrow .track { height: 2px; background: var(--bt-grey-dark); position: relative; }
  .barrow .fill {
    position: absolute; left: 0; top: 0; height: 2px; width: 100%;
    transform: scaleX(0); transform-origin: left;
    transition: transform 0.45s cubic-bezier(0.2, 0.7, 0.2, 1);
  }
  /* The Future is deliberately unscored, so only the module name is dimmed to
     read as held back (white at 48 percent still measures 4.99:1). The
     subtitle is left at full strength rather than inheriting that dimming:
     the grey it uses was measuring 2.2:1 dimmed, a real AA failure, and the
     "held back" read does not need the subtitle to carry it too. Future has
     no bar or subtitle beneath it, so it closes tighter than a scored row
     (3px, not 8px) to avoid reading as an unlabelled fifth row: the tighter
     close reinforces the "held back" read the dimmed name already carries. */
  .barrow.future { margin-bottom: 3px; }
  /* Was white at 48% with no track: three stacked grey lines that read as
     a broken fourth row. The name takes the module's own blue, lightened
     to stay legible on the card (--bt-future itself measures 3.9:1 here,
     under AA for this size; the mix measures well over 4.5:1), and an
     EMPTY dotted track sits where the other rows' tracks sit, so the row
     shares their skeleton and reads as deliberately unscored. The Bible
     asks for blue at 30% and no bar: 30% blue measures ~1.3:1 and cannot
     be read (the previous white-at-48% was already a documented deviation
     for the same reason), and an empty dotted track is not a bar - it is
     the absence of one, made visible. */
  .barrow.future .modname { color: color-mix(in srgb, var(--bt-future) 62%, #FFFFFF); }
  .barrow.future .track-empty { height: 0; border-top: 1px dotted color-mix(in srgb, var(--bt-future) 55%, transparent); margin-top: 2px; }
  /* Matches .barrow .subt exactly, so the card's two quiet asides read as one
     deliberate register rather than two separately tuned greys. Last element
     inside the card now, so it carries no trailing margin. */
  #future-line { font-size: 12px; line-height: 1.4; color: var(--bt-grey); max-width: 680px; }

  /* ---- gate ----
     This is the conversion moment: a mono eyebrow parallels the card's own,
     then a real headline (not another mono label), and the button is sized
     to match its importance. */
  /* A hairline top edge and a fade-in, unconditionally: below 1000px this
     was the whole of #gate's presentation, so the conversion moment was
     bare, un-choreographed text, fully visible from t=0 while the card's
     entrance played above it. The desktop rule below upgrades the hairline
     to a full frame; both widths share the same entrance now. */
  #gate {
    max-width: 560px; margin-top: 0;
    border-top: 1px solid var(--bt-grey-dark);
    padding-top: 32px;
    opacity: 0; transform: translateY(8px);
    transition: opacity 0.3s ease-out, transform 0.3s ease-out;
  }
  .results-top.cta-in #gate { opacity: 1; transform: none; }
  #gate h2 {
    font-family: var(--body);
    font-size: clamp(24px, 3vw, 32px); font-weight: 500;
    line-height: 1.2; letter-spacing: -0.018em;
    color: var(--bt-white); text-transform: none;
    margin-bottom: 16px; text-wrap: balance;
  }
  .notes-h {
    font-family: var(--mono); font-size: 11px; letter-spacing: 0.18em;
    text-transform: uppercase; color: var(--bt-grey); margin-bottom: 18px;
    /* Explicit rather than relying on the browser's default <h2> bold: Plex
       Mono only has 400/500 loaded (no 700), so an unstyled bold heading was
       already resolving to 500 by fallback. Naming it here makes that a
       decision instead of an accident. */
    font-weight: 500;
  }
  #gate .micro { font-size: 16px; line-height: 1.6; color: var(--bt-grey); margin-bottom: 40px; }
  .fieldwrap { margin-bottom: 26px; }
  .fieldwrap label {
    display: block;
    font-family: var(--mono); font-size: 11px; letter-spacing: 0.18em;
    text-transform: uppercase; color: var(--bt-grey); margin-bottom: 8px;
  }
  .fieldwrap input {
    width: 100%; background: transparent; border: 0;
    border-bottom: 1px solid var(--bt-grey-mid);
    color: var(--bt-white); font-family: var(--body); font-size: 16px;
    padding: 10px 0; border-radius: 0;
    /* Every other focus/hover state on the page eases; this one used to
       snap. */
    transition: border-bottom-color 0.18s ease;
  }
  .fieldwrap input:focus { border-bottom-color: var(--bt-grey); outline: none; }
  .fieldwrap .err {
    display: none;
    font-family: var(--mono); font-size: 11px; letter-spacing: 0.18em;
    text-transform: uppercase; color: var(--bt-white); margin-top: 8px;
  }
  /* The invalid-state colour lives on the input's own underline, a UI element,
     not on the error copy beside it. */
  .fieldwrap.bad input { border-bottom-color: var(--bt-mirror); }
  .fieldwrap.bad .err { display: block; }
  /* Sharp corners, matching the rest of the system. The card is the one
     rounded object on the page, not a shape the rest of the UI echoes. */
  /* Was a flat --bt-mirror red at rest -- the client's own read was "too
     bright", and the landing page (the same client, the sister product)
     never puts red on a surface at rest either: red there is exclusively
     a hover/active colour (.btn-primary), the resting state is charcoal.
     Ported that recipe exactly rather than inventing a funnel-specific
     one, since a unified button language across the two products was
     already on the table. */
  .blockbtn {
    display: block; width: 100%;
    background: #3D3B36; color: #F5F4F2;
    border: 1px solid rgba(255, 255, 255, .12);
    font-family: var(--mono); font-size: 14px; letter-spacing: 0.18em;
    text-transform: uppercase; text-align: center;
    padding: 17px 16px; margin-top: 18px; text-decoration: none;
    transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease, transform 0.1s ease;
  }
  /* White text measures 10.18:1 against the charcoal rest state but only
     3.55:1 against this red -- fails AA (needs 4.5:1) at 14px. Black
     clears 5.39:1 against the same red, so the label switches colour on
     hover rather than carrying one colour that is only safe on one of
     the two backgrounds. Confirmed by computing both, not assumed from
     the landing page's own version of this button. */
  .blockbtn:hover { background: #F1393A; border-color: transparent; color: #000000; }
  /* Tactile feedback: every control acknowledges the press. */
  .blockbtn:active { transform: translateY(1px); }
  .textlink:active, .opt:active { transform: translateY(1px); }

  /* ---- case notes ---- */
  /* Centred rather than left-flush: below the wide two-column card+gate row,
     a 680px reading column pinned to the left edge left 500px of dead space
     on the right at desktop widths and read as the page going lopsided. */
  /* scroll-margin-top, not a change to the scroll call itself: renderNotes()
     scrolls this element to the viewport top, and the fixed chrome bar (its
     own task, ~54px incl. inset) now sits over that top edge, opening the
     notes on a heading sliced in half. This is the scroll target's own
     offset, so it works regardless of what scrolls it into view. */
  #notes { display: none; max-width: 560px; margin: 0 auto; scroll-margin-top: 88px; }
  .restart-wrap { text-align: center; margin: -40px 0 40px; }
  #notes.active { display: block; }
  .notes-h { margin-top: 64px; }
  /* The notes read as a document being handed over, not a wall of text: each
     section (heading plus what follows it) fades up 60ms after the last. */
  #notes.stagger .notes-h,
  #notes.stagger p,
  #notes.stagger .tq {
    opacity: 0; transform: translateY(8px);
    animation: note-in 0.4s cubic-bezier(0.2, 0.7, 0.2, 1) forwards;
    animation-delay: calc(var(--i, 0) * 60ms);
  }
  @keyframes note-in { to { opacity: 1; transform: translateY(0); } }
  #notes p { font-size: 16px; line-height: 1.7; color: #C9C9C9; margin-bottom: 18px; }
  .tq { display: flex; gap: 18px; margin-bottom: 18px; }
  .tq .n {
    font-family: var(--mono); font-size: 11px; letter-spacing: 0.18em;
    color: var(--bt-grey); padding-top: 4px;
    margin-right: 18px; /* gap fallback */
  }
  @supports (gap: 1px) {
    .tq .n { margin-right: 0; }
  }
  .tq .t { font-size: 16px; line-height: 1.6; color: #C9C9C9; }

  #closing { margin-top: 80px; padding-top: 44px; border-top: 1px solid var(--bt-grey-dark); max-width: 560px; margin-left: auto; margin-right: auto; }
  .site-footer {
    margin-top: 64px; padding-top: 24px;
    border-top: 1px solid var(--bt-grey-dark);
    font-family: var(--mono); font-size: 12px; letter-spacing: 0.02em; color: var(--bt-grey);
    display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 16px 24px;
  }
  .site-footer-brand { display: flex; align-items: center; gap: 12px; }
  .site-footer-sep { width: 1px; height: 16px; background: var(--bt-grey-dark); }
  .site-footer-logo { height: 13px; width: auto; color: var(--bt-white); }
  .site-footer-milkman { height: 12px; width: auto; color: var(--bt-grey); }
  .site-footer-links { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 20px; text-transform: uppercase; letter-spacing: .08em; }
  .site-footer-links a { color: var(--bt-grey); text-decoration: none; }
  .site-footer-links a:hover { color: var(--bt-white); }
  @media (max-width: 480px) {
    .site-footer { flex-direction: column; align-items: flex-start; }
    .site-footer-links { flex-direction: column; align-items: flex-start; gap: 8px; }
  }
  #closing p { font-size: 16px; line-height: 1.7; color: #C9C9C9; margin-bottom: 18px; }
  #closing .blockbtn { margin-top: 36px; }
  /* Reuses .textlink's slide-in underline and active-press (below) via a
     class on the element, the same grammar as Begin and Learn more, instead
     of its own permanent static underline: the closing's final control was
     the one link on the page that never acknowledged a hover or a press. */
  #talk-link { margin-top: 26px; font-size: 12px; text-decoration: none; }

  @media (prefers-reduced-motion: reduce) {
    .barrow .fill { transition: none; }
    #rule { transition: none; }
    #sweep { display: none; } /* the 700ms sweep cue is pure motion */
    .opt::after { display: none; }
    .opt .sq { transition: background 0.15s ease; }
    .opt { animation: none; opacity: 1; transform: none; }
    .opt .sq.pulse { animation: none; }
    .qcount b { animation: none; opacity: 1; transform: none; }
    #notes.stagger .notes-h,
    #notes.stagger p,
    #notes.stagger .tq { animation: none; opacity: 1; transform: none; }
    .intro .eyebrow, .intro-marks, .intro h1, .intro p,
    .textlink { animation: none; opacity: 1; transform: none; }
    /* The two-colour ink stays, the drift stops: the colour is information
       (which modules held up), the movement is decoration. */
    #cond-name { animation: none; }
    .textlink::after { transition: none; }
    #gate { transition: none; }
  }

  /* Windows High Contrast forces background-color and color, so the filled
     square and the white label both disappear. Give the checked row a border
     the forced palette keeps. */
  @media (forced-colors: active) {
    .opt .sq { border: 1px solid ButtonBorder; }
    .opt[aria-checked="true"] .sq { background: Highlight; border-color: Highlight; }
    .opt[aria-checked="true"] { outline: 2px solid Highlight; outline-offset: 2px; }
    .options.locked .opt:not([aria-checked="true"]) .txt { color: GrayText; }
    /* The name is painted by a text-clipped gradient. A forced palette can
       drop the background and leave a transparent fill with nothing behind
       it, so the headline would vanish outright. Give up the sheen here and
       hand the name back to the system palette. */
    #cond-name {
      background-image: none;
      -webkit-text-fill-color: CanvasText;
      color: CanvasText;
      animation: none;
    }
  }

  @media (max-width: 560px), (max-height: 620px) {
    .screen { padding: 66px 20px 44px; }
    .opt .txt { font-size: 16px; }
    #results { padding: 90px 20px 70px; }
    /* Keeps the CTA label on one line in a narrow card. */
    .blockbtn { font-size: 12px; letter-spacing: 0.12em; padding: 18px 12px; }
  }
  /* A second, tighter tier for genuinely short viewports -- landscape phones
     mainly. The tier above alone still left only ~2 of 4 options visible at
     844x390 (measured), with nothing to signal the rest were one scroll
     away. This does not chase "everything on screen with no scroll" (the
     screen already scrolls, by design, for exactly this case); it shrinks
     the fixed costs -- the counter, the stem, each option row -- enough
     that a visitor sees most or all four without having to go looking. */
  @media (max-height: 460px) {
    .screen { padding: 52px 20px 40px; }
    .qcount { margin-bottom: 12px; }
    .stem { font-size: clamp(17px, 3.6vh, 22px); line-height: 1.2; margin-bottom: 14px; }
    .opt { min-height: 46px; padding: 10px 10px 10px 0; }
    .opt .txt { font-size: 15px; }
  }
