/* ═══════════════════════════════════════════════════════════════════════════
   THE LATTICE SKIN

   Loaded after scrollcraft.css, brand.css and assessment.css, and it is the
   last word on all three. The instrument's own stylesheet is entirely
   token-driven — --paper, --ink, --line, --card, --lead — so flipping the
   ground from bone to Midnight is mostly six values rather than a rewrite.
   What is left is the parts that only exist because this build puts the quiz
   inside a world: the canvas, the column scrim, the chrome, and the portrait.

   MIDNIGHT IS NOT A NEW COLOUR. #151524 is the brand's decided dark ground
   (brand.css, --onlead). The canvas here is one step under it so the lattice
   has something to be lit against.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── the palette ───────────────────────────────────────────────────────────
   TWO GROUNDS, ONE ACCENT, AND EVERY COLOUR IN THIS BLOCK.

   Nothing below this section names a colour: the surfaces, the scrim, the
   button hovers and the world's own wireframe all read tokens, which is what
   makes the light theme a second token block instead of a second stylesheet
   or a second renderer. If a rule further down hardcodes a value, it is a bug
   in this file, not a shortcut — that is exactly how the shared stylesheet's
   literal `#fff` on .back survived a ground flip and rendered a white slab on
   Midnight.

   Signal orange is the accent in BOTH themes. One accent for the page is the
   rule; a page that grows a second hue when the ground flips has two. */
:root,
:root[data-theme="dark"] {
  --paper:  #0D0D17;   /* the void. One step under Midnight, never pure black. */
  --rail:   #12121F;
  --card:   #191927;
  --line:   #2B2B3D;
  --ink:    #F3F1EB;   /* bone */
  --text2:  #ACA9B8;   /* tinted off the ground's hue, never flat gray */
  --mute:   #837F92;
  --faint:  #5A5769;
  --lead:   #FC7800;   /* Signal. One accent, and it owns the whole world. */
  --lead2:  #F83D04;
  --hit:    #FF3D14;   /* the reader's own bar — see the --hit note below */
  --tile:   #FE7001;
  --onlead: #151524;

  /* surfaces that have to invert with the ground */
  --scrim:      13, 13, 23;             /* the scrim's base, as an rgb triple */
  --soft:       rgba(25, 25, 39, 0.66); /* options, chips, translucent panels  */
  --solid:      rgba(25, 25, 39, 0.86); /* callouts and ghost buttons          */
  --back-bg:    transparent;
  --back-ink:   #ACA9B8;
  --back-line:  #2B2B3D;
  /* AN ORANGE CTA STAYS ORANGE ON HOVER. The shared stylesheet inverts it to
     a white ground, and Scotty's call is that it should not: "the orange CTAs
     should not fade to white on hover, they should stay orange." The press —
     translate onto the shadow, shadow collapses — is doing the work of saying
     "this is live" already, so the fill only has to deepen a step rather than
     become a different object. Blaze 3 is that step, and it is the same
     deepening .bsolid:hover already used. */
  --hover-bg:   #F83D04;
  --hover-ink:  #fff;
  --choice-line: #2B2B3D;               /* the resting edge on a choice */
  /* The hard 4px offset. Ink on the site's bone ground; here neither of the
     obvious translations works — a bone slab under every button is the loudest
     object on the page, and a Blaze-tinted one puts ten orange rectangles on a
     ten-option question and makes every unselected choice read half-selected.
     Rendered and rejected in that order.

     A near-black shadow is the honest answer on a ground this dark: the button
     surfaces sit ABOVE the canvas, so something darker than the canvas still
     reads as cast. Geometry, offset and timing are the brand file's; only the
     colour follows the ground. */
  --btn-shadow: rgba(0, 0, 0, 0.66);
  /* the bar's one control: bone on Midnight here, the inverse on the light ground */
  --nav-bg:   #F3F1EB;
  --nav-ink:  #151524;
  --nav-edge: transparent;
  /* Signal used as SMALL TEXT rather than as a fill. On the dark ground the
     accent itself measures 7.2:1 and needs no help. */
  --lead-text: #FC7800;
  /* The primary keeps a trace of the accent, because it is one button and it
     is meant to be the loudest thing in its own row. */
  --btn-shadow-key: rgba(252, 120, 0, 0.34);
  /* A 1px top highlight sells a raised surface better than blur does, because
     real lips catch light. It is what lets the shadow stay quiet. */
  --edge-lip: rgba(243, 241, 235, 0.07);
  --grain:      0.045;
  --logo-flip:  invert(1) hue-rotate(180deg) saturate(1.25) brightness(1.06);

  /* the world. A canvas cannot inherit a custom property, so world.js reads
     these six by name and re-reads them when the theme attribute flips. */
  --w-line:   #F2F0EA;
  --w-line-a: 0.42;
  --w-dust:   #F2F0EA;
  --w-dust-a: 0.055;
  --w-glow:   252, 120, 0;
  --w-glow-a: 0.13;
  --w-edge:   #FFD2A8;
  --w-edge-a: 0.55;

  /* Browser surfaces, themed off the same accent — selection, caret, focus. */
  --sc-canvas: #0D0D17;
  --sc-surface: #191927;
  --sc-ink: #F3F1EB;
  --sc-ink-soft: #ACA9B8;
  --sc-accent: #FC7800;
  --sc-accent-ink: #151524;

  /* NO RADIUS, ANYWHERE. brand.css ships `--r: 6px` and the canonical
     /assessment overrides it to 0 in its own inline style, under the note that
     law 8 is "no blur, no radius on structural cards". This build never made
     that override, so every surface reading the token — the rank callout, the
     level chips, the reaction bubble, the gate's email field, the shared
     primary button — came out with a 6px corner on a page whose entire visual
     system is squares. It was the one thing on the results page that belonged
     to a different design.

     The pills that /assessment deliberately exempts are already square here on
     Scotty's instruction, so there is nothing left to carve out. */
  --r: 0;

  --ease: cubic-bezier(0.23, 1, 0.32, 1);
  --nav-h: 74px;
  --rail-w: 196px;
  /* One definition of where the report ends. The results column, the divider
     and (through the divider) the portrait's panel all read it. */
  --report-w: min(58vw, 830px);
}

/* THE LIGHT GROUND IS THE SITE'S OWN, not a lightened version of the dark one.
   Every value here is lifted from assessment.css's :root — the bone, rail,
   card, line and the four ink steps that the rest of claudeguys.com already
   runs on. A light theme invented from scratch beside a real one is how two
   pages of the same site end up subtly different whites. */
:root[data-theme="light"] {
  --paper:  #FBFBF8;
  --rail:   #F5F4EF;
  --card:   #FFFFFF;
  --line:   #E4E3DE;
  --ink:    #151524;
  --text2:  #4A4943;
  --mute:   #6E6D66;
  --faint:  #9C9B93;
  --lead:   #FC7800;
  --lead2:  #F83D04;
  --hit:    #E21200;   /* deepest rung of the Blaze ramp — see the --hit note */
  --tile:   #FE7001;
  --onlead: #151524;

  --scrim:      251, 251, 248;
  --soft:       rgba(255, 255, 255, 0.74);
  --solid:      #FFFFFF;
  --back-bg:    #FFFFFF;
  --back-ink:   #151524;
  --back-line:  #151524;
  /* The shared stylesheet's own hover: white ground, Midnight label, Signal
     ring. On a light page that rule was always right, so the light theme
     simply gives it back its two colours. */
  --hover-bg:   #F83D04;
  --hover-ink:  #fff;
  /* "The white buttons should have black outlines unless selected, then they
     can be orange." On bone, --line is #E4E3DE and a choice drawn in it barely
     had an edge at all. */
  --choice-line: #151524;
  --btn-shadow: #151524;                /* assets/brand/button.css, verbatim */
  /* White with an ink border — the brand file's unfilled button. */
  --nav-bg:   #FFFFFF;
  --nav-ink:  #151524;
  --nav-edge: #151524;
  /* SIGNAL IS NOT A TEXT COLOUR ON BONE. #FC7800 on #FBFBF8 measures 2.59:1 —
     the same number the shared stylesheet already records for white-on-Signal,
     and for the same reason: it is a FILL colour, and this build was using it
     for the eyebrow, the level line and the callout's bold figure. One step
     down the same hue clears 4.5 and still reads as the accent rather than as
     brown. Measured, not chosen: 4.64:1. */
  --lead-text: #B85600;
  --btn-shadow-key: #151524;
  --edge-lip: transparent;   /* a lit lip on bone is invisible and pointless */
  --grain:      0.03;
  --logo-flip:  none;

  /* Dark hairlines on bone, and a much weaker horizon: an orange wash that
     reads as atmosphere against a void reads as a stain against paper.
     The line alpha is HIGHER than it looks like it should be, because the
     depth grading multiplies it down to 42% at the far end — at 0.20 the back
     of the runway was rendering at 0.084 and simply was not there. */
  --w-line:   #151524;
  --w-line-a: 0.34;
  --w-dust:   #151524;
  --w-dust-a: 0.045;
  --w-glow:   252, 120, 0;
  --w-glow-a: 0.05;
  --w-edge:   #FFFFFF;
  --w-edge-a: 0.42;

  --sc-canvas: #FBFBF8;
  --sc-surface: #FFFFFF;
  --sc-ink: #151524;
  --sc-ink-soft: #4A4943;
}

html, body { height: 100%; margin: 0; overflow: hidden; background: var(--paper); }

/* Light type on a dark ground needs compensating on three axes: a touch more
   line height, a touch more tracking, one step more weight. Set with
   light-ground metrics it reads thin and blurry, and that is why. */
body {
  background: var(--paper);
  color: var(--ink);
  line-height: 1.6;
  letter-spacing: 0.004em;
  -webkit-font-smoothing: antialiased;
}

::selection { background: var(--lead); color: var(--onlead); }
:focus-visible { outline: 2px solid var(--lead); outline-offset: 3px; border-radius: 0; }

/* ── the world ────────────────────────────────────────────────────────────── */
/* --world-fade is driven from JS while a phone's results page is scrolled; it
   is 1 everywhere else, so this costs nothing on desktop. */
.stage { position: fixed; inset: 0; z-index: 0; opacity: var(--world-fade, 1); }
.stage canvas { display: block; width: 100%; height: 100%; }

/* Grain. A flat dark ground bands on real displays, and 4% of noise is the
   difference between "a dark page" and "a lit room". */
.grain {
  position: fixed; inset: 0; z-index: 1; pointer-events: none; opacity: var(--grain);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* THE SCRIM IS A SIBLING OF THE COPY, NOT ITS CHILD. The verification harness
   hides the copy element and everything inside it to photograph the frame
   underneath, so a ::before on the text block would be hidden with it and the
   contrast it provides would never be measured.

   And it is a COLUMN, not a full-frame overlay. The copy holds one side of the
   frame for the whole run, so darkening the whole picture to protect it would
   throw the lattice away to fix one corner. */
.scrim {
  position: fixed; z-index: 2; pointer-events: none;
  top: 0; bottom: 0; left: 0; width: min(50vw, 720px);
  background: linear-gradient(90deg,
    rgba(var(--scrim), 0.97) 0%,
    rgba(var(--scrim), 0.95) 46%,
    rgba(var(--scrim), 0.74) 76%,
    rgba(var(--scrim), 0) 100%);
}

/* ── the chrome ───────────────────────────────────────────────────────────
   Live-surface grammar: the page behaves like the instrument, so app chrome
   replaces marketing nav. The rail is the position readout and it is the only
   persistent furniture on the page. */
.site {
  position: fixed; z-index: 60; background: rgba(var(--scrim), 0.84);
  backdrop-filter: blur(14px); border-bottom: 1px solid var(--line);
}
.site img { filter: var(--logo-flip); }
/* THE NAV CTA. Filled with --ink and labelled --paper it came out as a black
   slab on the bone theme — the heaviest object in the bar. Scotty asked for it
   white with a black border there, which is also what assets/brand/button.css
   says an UNFILLED button is: "a filled button has no border; an unfilled one
   has an ink border."

   Tokenised rather than scoped to a theme selector, so the dark ground gets the
   inverse for free — bone fill, Midnight label — and there is one rule instead
   of two that can drift. */
/* It is the last item of the links row now rather than a sibling of it, so it
   needs the row's own gap in front of it to sit where it always sat. */
.site .links .cta-nav { margin-left: clamp(16px, 2.4vw, 32px); }

.site .cta-nav {
  background: var(--nav-bg); color: var(--nav-ink);
  border: 1px solid var(--nav-edge);
  box-shadow: 3px 3px 0 var(--btn-shadow); border-radius: 0;
}
.site .cta-nav:hover { background: var(--lead); color: var(--onlead); }
.site .navpanel { background: var(--card); border-color: var(--line); }

.rail {
  z-index: 30; width: var(--rail-w);
  background: linear-gradient(90deg, rgba(var(--scrim), 0.9), rgba(var(--scrim), 0.5));
  border-right: 1px solid var(--line);
}
.rail .prog-n { color: var(--ink); font-variant-numeric: tabular-nums; }
.rail .prog-lb, .rail-hd, .rail-ft { color: var(--mute); }
.rail .lv .dot { background: transparent; border-color: var(--faint); }
.rail .lv.on .dot, .rail .lv.now .dot { background: var(--lead); border-color: var(--lead); }
.rail .lv .nm { color: var(--mute); }
.rail .lv.now .nm, .rail .lv.on .nm { color: var(--ink); }
.rail .lv .num { color: var(--faint); }

/* A depth readout, because a world you are travelling through should say where
   in it you are. Real numbers only: this counts cells actually laid out of 24,
   which is the reader's own answers and nothing invented. */
.depth {
  position: fixed; z-index: 30; left: 0; bottom: 0; width: var(--rail-w);
  padding: 16px 18px 26px; border-right: 1px solid var(--line);
  background: linear-gradient(90deg, rgba(var(--scrim), 0.9), rgba(var(--scrim), 0.36));
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--text2); line-height: 1.9;
}
.depth b {
  display: block; font-family: var(--display); font-size: 22px; font-weight: 500;
  letter-spacing: -0.02em; color: var(--ink); font-variant-numeric: tabular-nums;
  text-transform: none;
}
body.done .depth { opacity: 0; pointer-events: none; transition: opacity 0.4s var(--ease); }

/* ── the reading column ───────────────────────────────────────────────────── */
.deck {
  position: fixed; z-index: 20; left: var(--rail-w); top: var(--nav-h); bottom: 0;
  width: min(48vw, 620px); display: flex; align-items: center;
  padding: 0 clamp(24px, 3vw, 54px);
}
.deck > #mount { width: 100%; }
.deck .screen { width: 100%; }
.prerender .q { font-family: var(--display); font-weight: 500; letter-spacing: -0.03em; line-height: 1.06; }

.q { color: var(--ink); text-wrap: balance; }

/* ── "THE CLAUDE ASSESSMENT" IS THE PAGE'S TITLE, NOT A LABEL ──────────────
   2026-08-26 (Scotty): "the Claude Assessment should be a title up top on the
   page, not an eyebrow."

   The shared engine emits it as <p class="eyebrow"> directly above the h1, and
   at 10.5px mono uppercase grey it read as a kicker attached to the headline —
   a caption on the promise rather than the name of the thing you are on. It is
   now set as a title and lifted to the top of the reading column, where a page
   title belongs, leaving the headline to open on its own line.

   SCOPED TO `.screen > .eyebrow` DELIBERATELY. The same class is used twice
   more by the instrument — the results page puts "Level 4 of 5" in one inside
   .rhead, and it must stay a kicker there, because there it genuinely is one.
   The opener is the only screen with an eyebrow as a direct child of .screen,
   so the child combinator is the whole selector and no marker class is needed.

   Fixed rather than in flow: the deck is vertically centred, so an in-flow
   title would sit wherever the block happened to land rather than at the top of
   the page. Aligned to the same left edge as the copy below it — rail, then the
   deck's own padding — so it reads as the head of that column. */
/* The engine still emits its eyebrow; this skin shows the top-level title
   instead. Hidden rather than left in place, or the name appears twice. */
.screen > .eyebrow { display: none; }

.pagetitle { display: none; }
/* NEVER ON THE REPORT, whatever data-screen says. The engine clears the attr
   when it leaves the opener, but startPreview() jumps straight to the results
   without ever advancing a screen — so the attribute stayed "opener" and the
   title sat over the report in the one mode Scotty reviews it in. Stated as an
   invariant rather than patched in the preview path: the title names the page
   you are about to take, and the report is not that page. Declared after the
   rule below it wins on source order at equal specificity. */
body[data-screen="opener"] .pagetitle {
  display: block;
  position: fixed;
  top: calc(var(--nav-h) + clamp(20px, 2.4vw, 34px));
  left: calc(var(--rail-w) + clamp(24px, 3vw, 54px));
  margin: 0;
  font-family: var(--display);
  font-weight: 500;
  /* MUCH BIGGER — 2026-08-26 (Scotty): "the Claude Assessment text on the top
     should be much bigger of a header." At 21px it was still reading as a
     label that happened not to be uppercase. It stops short of the headline's
     38px cap on purpose: this names the page, the headline makes the promise,
     and the promise should stay the largest thing on the screen. */
  font-size: clamp(24px, 2.4vw, 34px);
  letter-spacing: -0.015em;
  color: var(--ink);
  z-index: 21;
  pointer-events: none;
}
body.done .pagetitle { display: none; }

/* ── THE OPENER'S BODY TEXT SITS AT 70% ────────────────────────────────────
   2026-08-26 (Scotty): "about 70% of the max width for the body text below
   Test Your Claude Proficiency." It ran the full column, which put it at the
   same measure as a 32px headline — technically fine, and it made the two read
   as one block of the same width instead of a claim with a note under it.

   Percentage, not a ch value, so it stays 70% OF THE COLUMN at every width
   rather than becoming the whole column on a narrow one. The heading is
   untouched: headlines get the full measure. */
.screen > .sub { max-width: 70%; }

/* ── A QUESTION GETS ONE LINE WHEREVER ONE LINE FITS ───────────────────────
   2026-08-26 (Scotty): "for questions like 'what do you do for work?', let's
   try and put those on one line if we can, unless it's genuinely too long."

   TWO THINGS WERE WRONG, and only one of them was the size.

   The first was the same defect the opener had: the shared ramp scales the
   type with the VIEWPORT while the reading column stops at 620px, so past
   ~1500px the words keep growing inside a box that has stopped. At 1920 the
   question reached 46px in a 512px box and EVERY question wrapped — "Have you
   fully automated anything using Claude?" ran to three lines. That is a bug at
   the widest desktops, not a length problem.

   The second is real length. Measured, the eight questions need these sizes to
   sit on one line in the 534px column at 1440:

     What do you do for work?                        45px
     What do you use Claude for?                     40px
     How often do you use Claude?                    38px
     What context do you give Claude?                34px
     Which Claude products do you use?               32px
     What tasks would you like to automate?          29px
     What tools does your Claude have access to?     26px
     Have you fully automated anything using Claude? 23px

   Fitting all eight means 23px, which makes the question timid and is not what
   was asked for — "unless it's genuinely too long" concedes that some are. 32px
   is the honest stopping point: the five shortest questions each get their one
   line, and the three that do not are the three that genuinely run long. */
.screen .q { font-size: clamp(25px, 2.3vw, 32px); }

/* ── THE OPENER'S HEADLINE IS TWO LINES, AND THEY ARE CHOSEN ────────────────
   It already broke after "proficiency" at 1440 — but only because the reading
   column happens to be that wide. Nothing held it there: a different viewport,
   a longer nav, a change to the column and it re-breaks somewhere else, most
   likely orphaning "up" onto a third line.

   `.q-lt` is the parenthetical, and the shared stylesheet already drops it to
   300 weight against the headline's 500 — the claim is the promise and the
   aside is the sweetener, and they are not meant to read as equals. Making it
   a block says the same thing about the line break: the claim gets a line, the
   aside gets a line, and no container gets a vote.

   `text-wrap: balance` comes OFF for this one heading. Balance exists to even
   out ragged lines a container chose; with the break already decided it has
   nothing to balance and will only fight the split. */
.q:has(.q-lt) { text-wrap: normal; }
.q .q-lt { display: block; }

/* THE CLAIM HAS TO FIT ON ITS LINE, AND AT 1920 IT DID NOT.
   The shared hero ramp scales with the VIEWPORT while the reading column is
   capped at 620px — so past about 1500px the type keeps growing inside a box
   that has stopped. At 1920 the font reaches 46px, the column is 512px after
   padding, and "Test your Claude proficiency" breaks across two lines, making
   the headline three lines instead of the two it is meant to be.

   The fix is a cap that respects the container rather than the window. 38px is
   the largest size at which the claim still fits the NARROWEST box it is ever
   given: measured at 502px against 512px of column at 1920, which is the worst
   case because the gutters grow with vw while the column does not.

   Deliberately NOT `white-space: nowrap`. That would guarantee one line by
   overflowing the column instead of wrapping in it, which trades a visible
   wrap for an invisible clip. Sized to fit and then verified by measuring the
   claim's own client rects at thirteen widths. */
.q:has(.q-lt) { font-size: clamp(28px, 2.7vw, 38px); }

/* At 390 the first line does not fit at the shared hero size — 27 characters
   at 26.5px is about 358px against a 350px column, so locking the break would
   simply have produced three lines instead of two. One rung down and it fits.
   Scoped with :has so it only touches the opener; every other question keeps
   the full size. Browsers without :has fall back to the old wrap, which is
   the behaviour that exists today rather than something worse. */
@media (max-width: 900px) {
  .q:has(.q-lt) { font-size: clamp(21px, 5.7vw, 27px); }
}
.sub, .why { color: var(--text2); max-width: 54ch; text-wrap: pretty; }
.eyebrow { color: var(--lead-text); }

/* ── the homepage button, applied here ────────────────────────────────────
   assets/brand/button.css is the one definition, and this build now follows
   it: no radius, a 1px edge, a 4px hard offset with no blur, and a hover that
   PRESSES the button down and right by exactly the shadow's offset while the
   shadow collapses to zero, so it lands on top of its own shadow and covers
   it. Not a lift — the brand file is explicit that a lift "reads as the button
   retreating from the cursor". Active adds scale(.985) and goes no further,
   because it is already sitting on the shadow.

   THE PILLS ARE SQUARE NOW TOO, and that is a change from the original
   instrument, which keeps a literal 999px on .opt and .pill under a note that
   "a pill is not a card". That exception earns its place on a page made of
   rounded cards. It does not earn it here: this page is a field of squares the
   reader is flying over, and a rounded choice sitting on top of a square-tile
   world was the one shape on screen that belonged to a different design.

   THE SHADOW COLOUR IS THE ONE THING THAT COULD NOT BE COPIED LITERALLY. The
   brand file draws it in --ink, which is Midnight on the site's bone ground.
   Here --ink is bone, so a literal copy paints a 4px bone slab under every
   button on a near-black page — the loudest thing in the frame. The light
   theme gets the brand value exactly; the dark theme keeps the geometry, the
   timing and the press, and tints the shadow. Same object, lit differently. */
:root { --btn-offset: 4px; }

.opt, .pill, .cta, .back, .bsolid, .bghost, .chip-now, .chip-next, .freetag,
.gate input[type="email"], .field, .paste {
  border-radius: 0;
}

/* ── "pick as many as you like" ────────────────────────────────────────────
   Q1 is already kind:"multi" and always was — three products can be selected
   at once. What was missing
   was any way to KNOW that before trying it: a row of identical buttons reads
   as pick-one, and Scotty read it that way.

   The fix is an affordance, not a subtitle. He removed Q1's subtext on purpose
   ("I would get rid of the subtext, I just think that's distracting"), so
   putting "select all that apply" back under the heading would undo a decision
   to fix a different problem. Instead every multi option carries a small empty
   SQUARE that fills when it is chosen — which is the brand's own mark, matches
   the tiles the reader is flying over, and needs no words. Driven entirely off
   the role the engine already sets, so there is no JS and no engine change. */
body[data-qkind="multi"] .pill { position: relative; padding-left: 40px; }
body[data-qkind="multi"] .pill::before {
  content: ""; position: absolute; left: 16px; top: 50%;
  width: 13px; height: 13px; margin-top: -7px;
  border: 1px solid currentColor; opacity: 0.5;
  transition: background 0.16s var(--ease), opacity 0.16s var(--ease);
}
body[data-qkind="multi"] .pill[aria-checked="true"]::before {
  background: currentColor; opacity: 1;
}

/* ── ONE QUESTION WEARS A DIFFERENT LAYOUT ───────────────────────────────
   The automation question was `kind: "choice"` and rendered as full-width list
   rows; making it multi-select moved it to the inline pill layout every other
   question uses, and the run lost its one change of shape. Scotty missed it:
   "I liked having it switch up to the different button styles like the list
   every once in a while."

   The engine picks the class from the kind and offers no hook for this, so the
   page publishes the question's id on <body> and the skin re-lays that one out.
   Multi-select is untouched — this is layout, not behaviour, and the checkbox
   squares carry on saying you can pick several. */
body[data-qid="auto"] .pills { flex-direction: column; align-items: stretch; }
body[data-qid="auto"] .pill { width: 100%; justify-content: flex-start; text-align: left; }

.opt, .pill {
  border: 1px solid var(--choice-line); background: var(--soft); color: var(--text2);
  backdrop-filter: blur(6px);
  box-shadow: var(--btn-offset) var(--btn-offset) 0 var(--btn-shadow),
              inset 0 1px 0 var(--edge-lip);
  transition: transform 0.14s cubic-bezier(0.2, 0, 0, 1),
              box-shadow 0.14s cubic-bezier(0.2, 0, 0, 1),
              border-color 0.14s cubic-bezier(0.2, 0, 0, 1),
              background 0.18s, color 0.18s;
}
@media (hover: hover) and (pointer: fine) {
  .opt:hover, .pill:hover {
    border-color: var(--lead); color: var(--ink);
    transform: translate(var(--btn-offset), var(--btn-offset));
    box-shadow: 0 0 0 var(--btn-shadow);
  }
}
.opt:focus-visible, .pill:focus-visible {
  transform: translate(var(--btn-offset), var(--btn-offset));
  box-shadow: 0 0 0 var(--btn-shadow);
}
.opt:active, .pill:active {
  transform: translate(var(--btn-offset), var(--btn-offset)) scale(0.985);
}
/* WHITE ON SIGNAL, AND IT STAYS WHITE. The shared stylesheet records the
   instruction twice and verbatim — "the orange bubbles should have white text
   in them, not black text, same goes for the continue button" — along with the
   measurement that white on #FC7800 is 2.59:1, and the note that Midnight on
   Signal was the 6.72:1 alternative that was looked at and set aside.

   This build had quietly swapped it to Midnight while flipping the page dark,
   which is not a dark-mode adjustment, it is reversing a decision the user
   made on the record. Reverted. The trade is his to re-make, not this file's;
   it is one `color` value if he ever does. */
.opt[aria-checked="true"], .pill[aria-checked="true"] {
  border-color: var(--lead); background: var(--lead); color: #fff; font-weight: 600;
}
/* A selected choice keeps its orange through hover for the same reason the CTA
   does — it is already the answer, and turning it into something else under the
   cursor reads as un-selecting it. */
.opt[aria-checked="true"]:hover, .pill[aria-checked="true"]:hover {
  background: var(--lead2); border-color: var(--lead2); color: #fff;
}

.cta, .bsolid, .bghost, .back {
  border-radius: 0;
  box-shadow: var(--btn-offset) var(--btn-offset) 0 var(--btn-shadow);
  transition: transform 0.14s cubic-bezier(0.2, 0, 0, 1),
              box-shadow 0.14s cubic-bezier(0.2, 0, 0, 1),
              border-color 0.14s cubic-bezier(0.2, 0, 0, 1),
              background 0.18s, color 0.18s;
}
.cta { background: var(--lead); color: #fff; font-weight: 600; border: 1px solid transparent; }
.cta, .bsolid { box-shadow: var(--btn-offset) var(--btn-offset) 0 var(--btn-shadow-key); }
.cta:hover:not([disabled]), .cta:focus-visible:not([disabled]), .cta:active:not([disabled]),
.bsolid:hover, .bsolid:focus-visible, .bsolid:active { box-shadow: 0 0 0 var(--btn-shadow-key); }

/* The press, on every button on the page. No border arrives on press — Scotty
   asked for that site-wide on 2026-08-25: "when you press them down they get a
   black border and I don't want that." */
.cta:hover:not([disabled]), .cta:focus-visible:not([disabled]),
.bsolid:hover, .bsolid:focus-visible,
.bghost:hover, .bghost:focus-visible,
.back:hover, .back:focus-visible {
  transform: translate(var(--btn-offset), var(--btn-offset));
  box-shadow: 0 0 0 var(--btn-shadow);
}
.cta:active:not([disabled]), .bsolid:active, .bghost:active, .back:active {
  transform: translate(var(--btn-offset), var(--btn-offset)) scale(0.985);
}

/* THE HOVER IS A REAL DARK-GROUND BUG, not a decision being reversed. The
   shared rule is `background:#fff; color:var(--ink)` — on the light page that
   is a white ground with a Midnight label, exactly as its comment describes.
   Here --ink resolves to bone, so hovering the primary button painted bone
   type on white and measured 1.13:1. The button did not dim, it vanished.

   The stated intent is "inverts rather than deepening the fill, with a 2px
   Signal ring". That idea survives the ground flip; the two literal colours do
   not. Inverting to the dark ground with a Signal label and the same ring is
   the same design, translated: 6.7:1, and quieter than a white flash on a
   Midnight page. */
/* The colour half of the hover. The shared stylesheet inverts to a hardcoded
   white ground, which is right on bone and paints bone-on-white here, so both
   ends are tokens now. */
.cta:hover:not([disabled]) { background: var(--hover-bg); color: var(--hover-ink); }
/* The disabled CTA measured 1.13:1 on a phone — not "quiet", invisible. A
   disabled control is exempt from the 4.5 floor and that exemption is not a
   licence to make it unreadable: the reader still has to see the thing they
   have not yet earned. A surface under it stops the contrast depending on
   whatever the world happens to be doing behind it. */
.cta:disabled {
  background: var(--solid); color: var(--mute);
  box-shadow: inset 0 0 0 1px var(--line);
}
.cta .ent { opacity: 0.7; }

/* .back CARRIES A LITERAL #fff IN THE SHARED STYLESHEET, not a token, so it
   survived the flip to a dark ground as a stark white slab — the loudest
   element on the page, attached to its least important action. Overriding the
   background as well as the ink is what this needs; setting only the colour is
   what put grey type on white at 3.88:1. */
.back {
  background: var(--back-bg); color: var(--back-ink);
  border-color: var(--back-line); font-weight: 500;
}
@media (hover: hover) and (pointer: fine) {
  .back:hover { background: var(--ink); color: var(--paper); }
}

.field, .gate input[type="email"], .paste, textarea, input[type="text"] {
  background: var(--soft); border-color: var(--line); color: var(--ink);
}
.field::placeholder, input::placeholder { color: var(--faint); }
.optin { color: var(--text2); }

/* ── THE TIPS ARE OFF ─────────────────────────────────────────────────────
   Hidden 2026-08-26 on Scotty's instruction, "get rid of the tips everywhere
   for now" — and "for now" is why this is a display rule rather than a delete.

   What they were: the three coaching lines that appeared under an answer, on
   `context`, `auto` and `tools`. They live as `reaction` on those questions in
   the shared assessment-core.js and are still rendered by the shared engine, so
   they are untouched for /assessment and one deleted line from coming back
   here. Nothing was removed from the instrument.

   The copy, so nobody has to go looking for it:
     context  "Bad output is usually a missing document, not a bad prompt."
     auto     "Anything you have to remember to start is still a job you own."
     tools    "If you picked nothing yet, you are the integration."

   No question carries a "Why we ask" line today, but .why goes with them: if
   one is ever added it should not reappear on its own while the tips are off. */
.pop, .why { display: none; }

.pop-in {
  background: var(--card); color: var(--ink); border-left-color: var(--lead);
  box-shadow: 0 14px 40px -18px rgba(0, 0, 0, 0.55);
}

/* The per-question visual host is empty in this build. The visual IS the world,
   which is the point: there is no second picture beside the question competing
   with the one the reader is flying over. */
.vis { display: none; }

/* ── the portrait ─────────────────────────────────────────────────────────
   The labels for the flattened 4x6 are real markup pinned to the canvas rect,
   never drawn into the canvas. Text baked into a picture is not selectable,
   not translatable and not sharp. */
.legend {
  position: fixed; z-index: 12; pointer-events: none; opacity: 0;
  transition: opacity 0.5s var(--ease);
  font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--mute);
}
.legend.on { opacity: var(--world-fade, 1); }
.legend .row {
  position: absolute; left: 100%; padding-left: 16px; white-space: nowrap;
  display: flex; align-items: center;
  transform: translateY(-50%);
}
.legend .cap {
  position: absolute; left: 0; top: 100%; padding-top: 14px;
  font-size: 9.5px; color: var(--mute); white-space: nowrap;
}

/* ── the result ───────────────────────────────────────────────────────────── */
/* THE RESULTS PAGE HAS TO COVER THE QUESTION SCREEN, and making it
   transparent is how it stopped doing that. The engine appends .results to
   <body> and leaves the last screen mounted underneath — it never removes it,
   because in every other presentation .results is an opaque fixed layer that
   hides it. Dropping the background to show the world through it left the
   gate's headline, email field and bullets legible under the verdict.

   So the deck goes on `body.done` instead, which is the class the engine
   already sets, and the reading column carries its own scrim as a real
   background rather than a negative-z pseudo-element. */
/* THE RAIL HAS TO GO WITH THEM. The shared stylesheet has no `.rail.done`
   rule at all — the engine sets the class and nothing reads it, because on
   /assessment the results page starts at left:190px and simply sits beside a
   rail that is still standing. This one starts at left:0, so the rail stayed
   underneath the copy and its orange "YOU" tag showed through the results as a
   small stray box with no explanation. */
body.done .deck, body.done .scrim, body.done .rail { display: none; }
/* The keyboard legend belongs to the questions. Left up, it reads through the
   results copy as a ghost line of text with nothing to explain it. */
body.done .kbd { display: none; }

/* ── the divider ──────────────────────────────────────────────────────────
   One hairline, only on the results screen, and it is the thing world.js
   measures the portrait's panel from — so the rule and the grid can never
   disagree about where the report ends.

   It fades in and out at the ends rather than butting into the header and the
   bottom edge, because a full-bleed rule on a page with no other full-bleed
   rules reads as a border somebody forgot to finish. */
.divider {
  position: fixed; z-index: 12; top: var(--nav-h); bottom: 0;
  left: var(--report-w); width: 1px; pointer-events: none;
  opacity: 0; transition: opacity 0.5s var(--ease);
  background: linear-gradient(180deg,
    rgba(var(--scrim), 0) 0%, var(--line) 12%, var(--line) 86%, rgba(var(--scrim), 0) 100%);
}
/* The divider waits for the step-back. The portrait spends the first four and
   a half seconds of the results page at full size, straddling the line the
   divider is drawn on, so appearing with the screen drew a rule through the
   middle of the reveal. */
body.done .divider { opacity: 1; transition-delay: 4.5s; }
@media (prefers-reduced-motion: reduce) { body.done .divider { transition-delay: 0s; } }

.results {
  /* THE READING COLUMN OUTWEIGHS THE MOSAIC HERE, which is the right way round
     and was not: at 700px against a full-size grid the two read as equals, and
     on the one screen that exists to be read the picture was winning. The
     column widens and the portrait condenses (world.js `condense`) so the copy
     is plainly the subject and the grid is what it refers to. */
  left: 0; top: var(--nav-h); width: var(--report-w); right: auto;
  padding: clamp(30px, 4.4vh, 60px) clamp(26px, 3.2vw, 60px) 96px;
  scrollbar-width: thin; scrollbar-color: var(--line) transparent;
  background: linear-gradient(90deg,
    rgba(var(--scrim), 0.985) 0%, rgba(var(--scrim), 0.97) 56%,
    rgba(var(--scrim), 0.82) 82%, rgba(var(--scrim), 0) 100%);
}
.results h1, .results h2 { color: var(--ink); text-wrap: balance; }
.results p, .results li { color: var(--text2); }

/* ── the full results page, ported ────────────────────────────────────────
   THIS IS /assessment's RESULTS PAGE, NOT A SUMMARY OF IT. An earlier pass of
   this build wrote its own — a level plate, a rank line and three pieces of
   advice — and dropped the thanks block, the distribution, the next-level
   ladder, the skill pack, the video and the share row on the way. Scotty asked
   for the older content back and he is right that it is the page: the sections
   below are the ones the funnel actually depends on, and a results page that
   ends without the pack, the room and the list ends without an ask.

   The structure and copy are the original's. What is different is that every
   colour reads a token, because the original's styles live inside a page whose
   ground is always bone and it hardcodes #fff in four places. */

/* The .thanks / .bl-mini / .bl-faces rules that stood here went with the block
   they styled (see index.html, "THE THANKS BLOCK IS GONE"). Dead CSS for a
   removed element is how a stylesheet stops being readable — if the block comes
   back, /assessment still carries the original rules to copy from. */

/* ── THE REPORT ASSEMBLES ─────────────────────────────────────────────────
   Each top-level section rises and fades in on its own beat, 110ms apart,
   starting after the swoop has begun. Six sections is 660ms of stagger, which
   is under the speed a reader gets down the page — so it reads as the report
   being built for them rather than as a queue they are waiting through.

   `--i` is set in JS, not by :nth-child, because two of the sections only
   exist for a capped or contradictory result — an nth-child ramp would number
   the same block differently depending on the answer. */
.results .seq {
  opacity: 0;
  animation: seq-in 0.62s var(--ease) both;
  /* HOLD IS THE SHIPPED ENTRANCE since 2026-08-26 — Scotty picked it out of the
     preview, so its timings are the BASE here rather than an override. The
     swoop happens on the gate, so the report is not waiting for anything and
     can afford the room: 300ms between sections against cascade's 130, and
     0.9s per arrival against 0.62.

     Slow is the cheapest way to look expensive and it is also the cheapest way
     to feel slow. This sits on the right side of that line at six sections;
     it would not at twelve. */
  animation-delay: calc(0.3s + var(--i, 0) * 0.3s);
  animation-duration: 0.9s;
  animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes seq-fade { from { opacity: 0 } to { opacity: 1 } }
@keyframes seq-in {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: none; }
}

/* The rank figure is set in <i> for counting, not for italics. */
.callout .tally-n { font-style: normal; font-weight: 600; color: var(--lead-text); font-variant-numeric: tabular-nums; }

/* ── THE VERDICT WIPES IN ─────────────────────────────────────────────────
   The one line on the page the reader came for gets more than a fade. A
   clip-path wipe upward reads as the sentence being set rather than appearing,
   and it is the sanctioned third property for motion alongside transform and
   opacity — no layout, no paint of anything underneath. */
.results .rhead h1 {
  animation: verdict 1.1s cubic-bezier(0.2, 0, 0, 1) both;
  animation-delay: 0.5s;
}
@keyframes verdict {
  from { clip-path: inset(0 0 100% 0); transform: translateY(10px); }
  to   { clip-path: inset(0 0 -12% 0); transform: none; }
}
/* The level number sits in an <i> so it can count; it is not italic. */
.eyebrow .lvl-n { font-style: normal; font-variant-numeric: tabular-nums; }

/* ── THE SECTION RULES DRAW THEMSELVES ───────────────────────────────────
   Each block's top border becomes a line that arrives from the left with its
   section instead of being there before it. scaleX on a 1px pseudo-element:
   no layout, and the border it replaces is removed so nothing is drawn twice. */
.results .block { border-top: 0; position: relative; }
.results .block::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 1px;
  background: var(--line); transform: scaleX(0); transform-origin: left center;
  animation: rule-in 1.1s cubic-bezier(0.2, 0, 0, 1) both;
  animation-delay: calc(0.34s + var(--i, 0) * 0.3s);
}
@keyframes rule-in { to { transform: scaleX(1); } }
.results .block:first-of-type::before { display: none; }

.rhead { text-align: left; margin-top: clamp(30px, 3.4vw, 46px); }
.rhead .eyebrow { color: var(--lead-text); }
.rhead h1 { margin: 0; line-height: 1.02; }
/* The verdict and its share control on one line. `baseline` rather than
   `center` so the glyph sits with the cap height of a 40px headline instead of
   floating at the midpoint of a line box that is mostly descender space. */
.rhead-row {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: clamp(12px, 1.4vw, 18px); margin-top: 10px; flex-wrap: wrap;
}
/* The headline takes what it needs and no more, which is what lets
   space-between push the control to the column's right edge rather than
   parking it one word after the full stop. */
.rhead-row h1 { flex: 0 1 auto; }
.shareicon {
  flex: none; width: 40px; height: 40px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; color: var(--mute);
  border: 1px solid var(--line); border-radius: 0;
  cursor: pointer; align-self: center;
  transition: color 0.16s var(--ease), border-color 0.16s var(--ease),
              background 0.16s var(--ease);
}
.shareicon svg { width: 19px; height: 19px; display: block; }
.shareicon:hover, .shareicon:focus-visible {
  color: var(--solid); background: var(--lead); border-color: var(--lead);
}
.rhead .lead { margin-top: 18px; font-size: 16.5px; line-height: 1.62; max-width: 60ch; }
/* Section labels get room above and sit tight to what they label. */
/* TIGHTER — 2026-08-26 (Scotty): "there's too much padding in the divider
   below Where you rank and Get to the next level and the thing below that."
   Measured before the change: 64px above each rule and 36 to 48 below it, so
   every divider on the report was costing 100px+ of vertical space and the
   sections read as separate pages rather than as parts of one report.
   Now 40 above and 24 below — still a clear break, two thirds of the cost. */
.results .block { margin-top: clamp(30px, 3.2vw, 40px); padding-top: clamp(18px, 1.9vw, 24px); }
/* ── "WHERE YOU RANK" IS A HEADING, NOT A MACHINE LABEL ────────────────────
   2026-08-26 (Scotty): "change the Where You Rank font, I hate that font with
   a passion."

   It was 10px IBM Plex Mono, uppercase, tracked out to .16em. That treatment
   is right for the things on this page that ARE machine output — the band
   labels down the mosaic, the ground-laid counter, the keyboard legend — and
   wrong for this, which is a section heading with the same job as "Give your
   Claude superpowers" a few hundred pixels below it. Two headings of equal
   rank were set in two different families.

   It now takes the display face like every other heading on the report, at a
   size that keeps it subordinate to the h2s that open the offers. The mono is
   untouched everywhere it belongs.

   The old contrast note still applies and is why this is --text2 rather than
   --faint: --faint measures 2.69:1 on bone and 2.76:1 on Midnight, fine for a
   rule or a tick and not for words somebody has to read. */
/* ⚠️ `.results .seclab`, NOT `.seclab`. The shared sheet carries
   `.block p { font-size: 16px }` at (0,1,1), which outranks a bare class at
   (0,1,0) — so the family and the casing here applied while the SIZE silently
   did not, and the label sat at 16px through two rounds of being told to grow.
   Nothing looked broken, which is why it took a computed-style read to find.
   (0,2,0) clears it. Same reason .rankhead .callout below already worked. */
.results .seclab {
  margin: 0; font-family: var(--display); font-weight: 500;
  /* BIGGER — 2026-08-26 (Scotty), a second pass on the same line. Moving it off
     the mono fixed the family; it was still sized like the label it used to be.
     Now it reads at the weight of the section it opens. */
  font-size: clamp(19px, 1.9vw, 24px); letter-spacing: -0.02em;
  text-transform: none; color: var(--ink);
}
/* ── THE SECTION HEADINGS ──────────────────────────────────────────────────
   2026-08-26 (Scotty): "the headers on these sections can look better, I just
   don't know how."

   What was wrong: they were unanchored. Three big headings floating in white
   space with nothing marking where one offer ended and the next began, so the
   report read as one long column of text with occasional larger text in it.
   The pack heading got away with it because its FREE SKILL PACK tag gave it
   something to sit against; the other two had nothing at all.

   Two devices, both already the brand's, neither invented for this page:

   1. A RULE above each one, so a section starts rather than merely continues.
      The .seclab blocks higher up the report already work this way — this
      extends the same separation to the headings that sit inside a block.

   2. ~~A BLAZE SQUARE before the words.~~ TRIED AND REMOVED the same day.
      The reasoning was the site's own SecHead device — components/sig.tsx puts
      a TileMark before every h2 because "the mark is what makes a plain h2
      belong to this brand" — and the echo of the mosaic beside it seemed to
      earn it twice over. Scotty: "take off the orange bullet points on the
      side of each header." Recorded rather than deleted so it is not
      rediscovered as a good idea: on a page whose right half is already a grid
      of Blaze squares the marks read as a second, smaller grid competing with
      the real one. The rules alone do the separating. */
.results .subsec {
  /* Same trim as .block above, and for the same complaint: these rules were
     62px + 27px apart. The heading marks are what separate the sections now,
     so the whitespace does not have to do it twice. */
  margin: clamp(26px, 2.8vw, 36px) 0 0;
  padding-top: clamp(14px, 1.5vw, 19px);
  border-top: 1px solid var(--line);
}

/* ── ONE DIVIDER PER SECTION, NOT TWO ──────────────────────────────────────
   A .block draws its own rule with ::before. A .subsec draws one with
   border-top. When a subsec is the FIRST thing in a block the two land within
   a few pixels of each other and the section opens with a double line — which
   is what the removed "Get to the next level" label was sitting inside. The
   block's rule wins, because it is the one that belongs to the section. */
.results .block > .subsec:first-child {
  margin-top: 0; padding-top: 0; border-top: 0;
}

/* ── A HEADING THAT CARRIES ITS STEP ───────────────────────────────────────
   "How to level up" holds the rung pair on its own line. Flex only when the
   chips are actually there, so the level-5 heading — which has no next rung —
   stays a plain heading and does not become a one-item flex row with the
   justify-content of a two-item one. */
.results .subsec:has(.chips) {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px 20px; flex-wrap: wrap;
}
.results .subsec .chips { flex: none; margin: 0; }
/* The copy under it gets the full measure back, which was the point. */
.results .subsec + .skillwhat { margin-top: 14px; }
/* The section label and the verdict on one line, the verdict trailing right. */
.rankhead {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px 20px; flex-wrap: wrap;
}
.rankhead .seclab { margin: 0; }
/* On a phone the pair does not fit one line; it wraps, and the stat goes back
   to reading left with the label above it rather than trailing off the right
   edge of its own row. */
@media (max-width: 700px) {
  .rankhead { gap: 6px; }
  .rankhead .callout { text-align: left; }
}
/* Trailing stat, not a second card. The bordered box was right when it sat
   alone under the label; on the label's own line two framed objects competed.
   Border and ground come off, the figure keeps its weight. */
.rankhead .callout {
  margin: 0; padding: 0; border: 0; background: none;
  /* Up from 13. It is the one real number in the section and it was set
     smaller than the body copy above it. Still a step under the heading it
     shares the line with, so the two do not compete. */
  font-size: clamp(14px, 1.25vw, 16.5px); color: var(--text2); text-align: right;
}
.callout {
  display: inline-block; margin: 10px 0 0; padding: 6px 11px;
  background: var(--solid); color: var(--ink); border: 1px solid var(--line);
  border-radius: var(--r); font-size: 12.5px; line-height: 1.4;
}
.callout b { color: var(--lead-text); font-weight: 600; }

/* the distribution. Real figures — LEVEL_MIX out of assessment-core.js. */
/* The distribution reads as a chart rather than as five slabs: taller, with
   real gutters, and the two small levels still legible at 9% and 3%. */
.dist {
  display: grid; grid-template-columns: repeat(5, 1fr); gap: clamp(6px, 0.8vw, 12px);
  /* TALLER — 2026-08-26 (Scotty): "let's make the graph chart more taller and
     dramatic." At 92px the 3% Conductor column was three pixels and the whole
     distribution read as a strip. At 170 the shape has somewhere to go and the
     drop from Prompter to Conductor is something you can see rather than infer.
     Growth is this chart's only animation, so a taller chart is also a longer
     one to watch arrive. */
  /* 44, not 22: the hover chip is anchored to the top of the chart box
     (bottom: 100% + 10 on .dcol), so at 22px it landed on the "Ahead of 88%"
     line sharing the heading row. This is the tooltip's headroom, not
     decoration — measured against the callout's rect, not eyeballed. */
  align-items: end; height: 170px; margin-top: 44px;
}

/* ── WHAT EACH COLUMN IS, ON HOVER ─────────────────────────────────────────
   attr(data-tip) is set where the column is built. Ink chip, square, mono —
   the same vocabulary as every other small label on the page. */
.dcol { position: relative; outline: none; }
.dcol::after {
  content: attr(data-tip);
  position: absolute; bottom: calc(100% + 10px); left: 50%;
  transform: translateX(-50%) translateY(3px);
  white-space: nowrap; pointer-events: none;
  font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.05em;
  background: var(--ink); color: var(--solid);
  padding: 6px 9px; border-radius: 0; z-index: 6;
  opacity: 0;
  transition: opacity 0.14s var(--ease), transform 0.14s var(--ease);
}
.dcol:hover::after, .dcol:focus-visible::after {
  opacity: 1; transform: translateX(-50%) translateY(0);
}
/* The outer two would hang off the column otherwise. */
.dist .dcol:first-child::after { left: 0; transform: translateY(3px); }
.dist .dcol:first-child:hover::after,
.dist .dcol:first-child:focus-visible::after { transform: translateY(0); }
.dist .dcol:last-child::after { left: auto; right: 0; transform: translateY(3px); }
.dist .dcol:last-child:hover::after,
.dist .dcol:last-child:focus-visible::after { transform: translateY(0); }
.dcol:focus-visible { box-shadow: 0 0 0 2px var(--lead); }

/* ── EVERY BAR ANSWERS THE POINTER ─────────────────────────────────────────
   2026-08-26 (Scotty): "it should have highlight effects and stuff when you
   hover over the different bars, like each individual bar on the chart."

   The tooltip was already per-column, which told you WHICH level you were over
   but left all fifteen bars looking identical while you did it. Hovering one
   now lifts it and drops everything else back, which is the standard reading of
   a distribution: this one, against the rest.

   `:has(.dbar:hover)` scopes the dimming to the moment a bar is actually under
   the pointer, so the chart sits at full strength the rest of the time and
   nothing fades on the way in. The reader's own bar is exempt — it is the one
   fact the section exists to state, and dimming it to look at a neighbour
   would take away the answer while they browse. */
.dbar { transition: opacity 0.16s var(--ease), filter 0.16s var(--ease),
                    height 1s cubic-bezier(0.16, 1, 0.3, 1),
                    background 0.62s var(--ease); }
.dist:has(.dbar:hover) .dbar { opacity: 0.38; }
.dist:has(.dbar:hover) .dcol.you .dbar.hit { opacity: 1; }
/* ⚠️ SCOPED INSIDE :has() ON PURPOSE, and it is a specificity fix rather than
   a style choice. `:has()` takes the weight of its most specific argument, so
   `.dist:has(.dbar:hover) .dbar` scores (0,4,0) while a plain
   `.dist .dbar:hover` scores only (0,3,0) — the dimming rule beat the hover
   rule and the bar under the pointer faded with all the others. Repeating the
   :has() here lifts this to (0,5,0). Measured, not guessed: the hovered bar
   was computing opacity 0.38. */
.dist:has(.dbar:hover) .dbar:hover {
  opacity: 1; filter: brightness(0.93) saturate(1.15);
}
.dcol { display: flex; flex-direction: column; justify-content: flex-end; height: 100%; }

/* ── THE CURVE VARIANT ─────────────────────────────────────────────────────
   Three bars per level instead of one. The grid is still five columns and the
   labels under it are unchanged — only the inside of each cell changes, so the
   categories keep their boundaries and the chart keeps being readable as five
   things rather than fifteen.

   The gap INSIDE a group is deliberately tighter than the gap BETWEEN groups.
   Equal gaps would dissolve the five categories into one row of fifteen bars
   and lose the grouping the labels depend on. */
.dist.curve .dcol { flex-direction: row; align-items: flex-end; gap: 2px; }
/* .dist without .curve is the five-block fallback, reachable only from
   ?preview. The curve is what ships. */
.dist.curve .dcol .dbar { flex: 1 1 0; }

/* ── WHERE IN THEIR OWN LEVEL ──────────────────────────────────────────────
   With three sub-bars, lighting all of a reader's level says "you are somewhere
   in this band", which is less than the five-bar chart already said. Only the
   third they actually sit in takes full Signal; the other two sit at the same
   muted tint as the levels they have passed.

   The column still reads as theirs at a glance — it is the only place on the
   chart where Signal appears at all — and now it also says where in it. */
/* Three states inside their own column, and the order is the argument: what
   they have covered, where they are, and what is still ahead. The hit is the
   LAST lit bar — nothing to the right of it takes any colour, because colouring
   it would claim ground they have not covered. */
/* ── --hit: THE READER'S BAR IS A DIFFERENT COLOUR, NOT MORE OF THE SAME ONE ──
   2026-08-26, off the review call. Austin, on a deliberately bright screen:
   "slightly hard to see — and I have my screen brightness fairly high — the
   difference between the other orange bars and that bar."

   He was right, and the cause was that every lit bar on the chart was the same
   hue. Passed levels are a 62% mix of --lead; the reader's own bar was --lead
   at 100%. That is a SATURATION step inside one colour, and saturation is the
   first thing a bright screen flattens — which is exactly the condition he was
   reading it under.

   The obvious fix, dropping the passed levels further, is already ruled out
   two rules down: 34% was tried and the counted state went invisible on bone.
   So the separation has to come from the other end. --hit is the deepest rung
   of the Blaze ramp (#E21200 on bone, lifted to #FF3D14 on Midnight where the
   deep red goes muddy), which differs from --lead in HUE as well as value.
   Red against orange survives a bright panel in a way orange against paler
   orange does not.

   Still no marker over the column — Scotty asked for that off and for colour
   alone to carry it. This is colour alone; it is just colour that actually
   separates. */
.dist.curve .dcol.you .dbar      { background: var(--line); }
.dist.curve .dcol.you .dbar.pre  { background: color-mix(in oklab, var(--lead) 62%, var(--line)); }
.dist.curve .dcol.you .dbar.hit  { background: var(--hit); }
.dist.curve .dcol.you .dbar.post { background: var(--line); }
.dbar { background: var(--line); min-height: 3px; transition: height 0.6s var(--ease); }
/* ── THREE STATES, AND THE DIFFERENCE BETWEEN THEM IS THE ARGUMENT ────────
   Levels the reader has PASSED light as the sweep counts them, and the tint is
   deliberately a step down from the accent rather than the accent itself: they
   are the evidence, not the answer. Levels ABOVE them stay in the hairline
   grey they started in and never move, because nothing has been claimed about
   them. The reader's own column is the only thing on the chart that is Signal.

   Read left to right after it has run, the chart says what the sentence above
   it says, without anybody having to do arithmetic. */
/* THE GROWTH IS THE ANIMATION. Every brightness flash on this chart is gone:
   a bar that rises into its height has already said everything a pulse was
   standing in for, and the two together read as blinking rather than as
   arriving. Scotty: "I don't like the blinking bars, I would rather they grow
   into their height."

   The flash was covering for a structural mistake rather than being a style
   choice. Growth is the strongest signal a bar chart has, and spending it up
   front on all five columns at once left nothing for the count to do — so the
   count needed a second, weaker signal invented for it. The sweep in
   index.html now grows each column at the moment it is counted, and the colour
   rides the same beat slightly quicker so a counted column takes its tint on
   the way up rather than after it lands. */
.dcol .dbar {
  transition: height 1s cubic-bezier(0.16, 1, 0.3, 1),
              background 0.62s var(--ease);
}
/* 62%, not 34%. At a third the mix landed a hair off --line on bone and the
   "counted" state was invisible — the sweep ran and nothing appeared to happen.
   It has to read as a change at a glance while still being clearly a step below
   the reader's own Signal column. */
.dcol.below.counted .dbar { background: color-mix(in oklab, var(--lead) 62%, var(--line)); }
.dfoot span { transition: color 0.4s var(--ease); }

.dcol.you .dbar { background: var(--hit); }   /* same separation in the 5-bar chart */
/* NO MARKER ABOVE THE BAR. A square dropped in over the reader's column was a
   second device saying what the colour already says — Scotty asked for it off
   and the colour alone to carry it. It can: theirs is the only Signal column on
   a chart where the levels they passed are a muted tint and the ones above are
   hairline grey, so it is the loudest mark there by a clear margin without
   anything pointing at it. Its label coming to full ink is the only other
   emphasis it gets, and that is enough. */
.dfoot {
  display: grid; grid-template-columns: repeat(5, 1fr);
  gap: clamp(6px, 0.8vw, 12px); margin-top: 10px;
  border-top: 1px solid var(--line); padding-top: 9px;
}
.dfoot span {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--mute); text-align: center;
  line-height: 1.55; white-space: pre-line; font-variant-numeric: tabular-nums;
}
.dfoot span.you { color: var(--ink); font-weight: 500; }

.chips { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-top: 26px; }
/* The step is a small piece of theatre and it is the last thing the section
   says: the rung they are on, then the arrow travelling, then the rung above
   arriving. Staggered off the section's own clock rather than the page's. */
.chips i { animation: arrow-go 0.5s var(--ease) both; animation-delay: 0.5s; }
@keyframes arrow-go {
  from { opacity: 0; transform: translateX(-8px); }
  to   { opacity: 1; transform: none; }
}
.chip-next { animation: next-pop 0.5s cubic-bezier(0.2, 0, 0, 1) both; animation-delay: 0.66s; }
@keyframes next-pop {
  from { opacity: 0; transform: scale(0.94); }
  to   { opacity: 1; transform: none; }
}
.chip-now, .chip-next { padding: 9px 14px; border-radius: var(--r); font-size: 13.5px; }
.chip-now { border: 1px solid var(--line); background: var(--soft); color: var(--text2); }
.chip-next { background: var(--lead); color: #fff; font-weight: 600; }
.chips i { font-style: normal; color: var(--mute); }

.subsec { margin: 34px 0 0; }
.freetag {
  display: inline-block; vertical-align: 3px; margin-left: 2px;
  font-family: var(--mono); font-size: 11px; font-weight: 600; letter-spacing: 0.1em;
  /* Square, like everything else. This is the only 999px left in the file and
     it was overriding the radius reset further up by sheer source order. */
  text-transform: uppercase; padding: 5px 10px; border-radius: 0;
  background: var(--lead); color: #fff; white-space: nowrap;
}
/* The wider column is what lets the two pack buttons sit side by side. At
   700px the copy cell was 384px and a 266 + 200 pair wrapped, which is where
   the section started looking cramped. */
.skillrow {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: clamp(20px, 2.4vw, 34px); align-items: start; margin-top: 24px;
}
/* NO BOX AND NO SHADOW. It arrived with a 12px corner and a soft blurred drop
   shadow, which was two law-8 violations in one line; the first fix put it in
   the same hard-offset frame the buttons wear, which was consistent and still
   wrong. It is an ILLUSTRATION — it already has its own edges, its own paper
   and its own drop shadow drawn into the artwork. Framing a picture of a
   document in a second document is one border too many, and it made the only
   drawn thing on the page look like a screenshot of something else. It sits on
   the ground now. */
.skillshot {
  /* Bigger, and taller than it was: with the frame gone it has to hold the
     left of that row on its own, and at 200px it read as a thumbnail next to
     a paragraph rather than as the thing being offered. */
  width: clamp(200px, 21vw, 290px); height: auto; display: block;
  border: 0; border-radius: 0; box-shadow: none; background: none;
}
.skillwhat { margin: 9px 0 0; font-size: 15px; line-height: 1.6; color: var(--text2); max-width: 56ch; }
.skillrow .ractions { margin-top: 18px; }

/* WHITE ON SIGNAL, same instruction as the pills and the primary button. */
.ractions { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 28px; }
.bsolid, .bghost {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  height: clamp(44px, 3.3vw, 48px); padding: 0 clamp(16px, 1.6vw, 26px);
  font-family: inherit; font-size: clamp(13px, 1.04vw, 17px); font-weight: 600;
  cursor: pointer; text-align: left; text-decoration: none;
  border-radius: var(--r); border: 1px solid transparent;
  transition: background 0.16s var(--ease), transform 0.14s var(--ease),
              box-shadow 0.14s var(--ease), color 0.16s var(--ease);
}
.bsolid { background: var(--lead); color: #fff; border-color: transparent; }
/* An unfilled button takes an ink edge — the brand file's edge rule, and the
   reason .btn-ghost is not allowed to be a shape without one. */
.bghost { background: var(--solid); border-color: var(--ink); color: var(--ink); }
@media (hover: hover) and (pointer: fine) {
  .bsolid:hover, .bsolid:focus-visible { background: var(--lead2); }
}

/* 16:9 held by the wrapper, not by the iframe, so it fills the column on a
   phone instead of overflowing it. nocookie, and lazy. */
.video {
  position: relative; margin-top: 24px; aspect-ratio: 16 / 9;
  max-width: 640px; border-radius: 0; overflow: hidden;
  background: #000; border: 1px solid var(--line);
}
.video iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

.joinq {
  margin: 0; font-family: var(--display); font-weight: 500; letter-spacing: -0.025em;
  font-size: clamp(21px, 2.2vw, 28px); line-height: 1.2; color: var(--ink);
}
.join .ractions { margin-top: 22px; }
.sharelink { margin: 24px 0 0; font-size: 13.5px; }
.sharelink a {
  color: var(--text2); text-decoration: underline; text-underline-offset: 3px;
  cursor: pointer;
}
.sharelink a:hover { color: var(--ink); }
.sharelink a.ok { color: var(--lead); text-decoration: none; font-weight: 600; }
/* The two tail links on one row, clear of the buttons above them. 38px rather
   than the 24 the share link used to carry alone: the row now holds two
   controls and needs to read as its own band under the CTAs, not as a caption
   hanging off them. */
.sharetail {
  display: flex; align-items: baseline; flex-wrap: wrap;
  gap: 8px 22px; margin-top: 38px;
}
.sharetail .sharelink { margin: 0; }

/* Quieter than the share link beside it. */
.startover { margin-top: 0; }
.startover a { color: var(--faint); }
.startover a:hover { color: var(--text2); }

/* ── THE SHARE SHEET ────────────────────────────────────────────────────────
   Square, hard-shadowed, no radius — assets/brand/button.css law 8, same as
   every other structural surface on this page. A native <dialog>, so the top
   layer, the backdrop, Esc and focus containment are the platform's job.

   `:modal` rather than `[open]` for the layout rules: a <dialog> opened with
   .show() instead of .showModal() is also [open] but is NOT in the top layer,
   and centring it with fixed positioning in that state would put it over the
   page with nothing inert behind it. Matching :modal means these rules only
   apply in the state they were written for. */
.sharedlg {
  border: 1px solid var(--ink); border-radius: 0; padding: 0;
  background: var(--solid); color: var(--ink);
  box-shadow: 6px 6px 0 var(--btn-shadow);
  max-width: min(560px, calc(100vw - 32px));
  width: min(560px, calc(100vw - 32px));
}
.sharedlg::backdrop { background: rgba(21, 21, 36, 0.62); }
.sharedlg:modal { margin: auto; }

.sharedlg-hd {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 18px 20px; border-bottom: 1px solid var(--line);
}
.sharedlg-hd h2 {
  margin: 0; font-family: var(--display); font-weight: 500;
  font-size: 19px; letter-spacing: -0.02em; color: var(--ink);
}
.sharedlg-x {
  flex: none; width: 30px; height: 30px; line-height: 1;
  font-size: 21px; font-family: inherit;
  background: transparent; border: 1px solid var(--line); border-radius: 0;
  color: var(--mute); cursor: pointer;
}
.sharedlg-x:hover { color: var(--ink); border-color: var(--ink); }

/* The card is 1200x630 and the frame states that ratio, so the dialog is its
   final height before the PNG lands rather than growing under the pointer. */
.sharedlg-card {
  display: block; width: 100%; height: auto; aspect-ratio: 1200 / 630;
  border-bottom: 1px solid var(--line); background: var(--soft);
}
.sharedlg-note {
  margin: 0; padding: 14px 20px 0;
  font-size: 13px; line-height: 1.5; color: var(--mute);
}
.sharedlg-acts { display: flex; flex-wrap: wrap; gap: 10px; padding: 14px 20px 0; }
.sharedlg-acts .bsolid.ok { background: var(--ink); color: var(--solid); }

/* The URL in full, because a share dialog that will not show you what it is
   about to put on your clipboard is asking for trust it has not earned. */
.sharedlg-url {
  margin: 0; padding: 14px 20px 20px;
  font-family: var(--mono); font-size: 11.5px; color: var(--faint);
  overflow-wrap: anywhere;
}

@media (max-width: 560px) {
  .sharedlg-acts .bsolid, .sharedlg-acts .bghost { flex: 1 1 auto; }
}


/* THE THEME SWITCH LIVES ABOVE THE PHONE BLOCK, and it did not, which is why
   the toggle jumped to the bottom of the screen on a phone's results page and
   landed on top of the rank callout. `body.done .themer { bottom: 18px }` was
   declared AFTER the media query that overrides it — same specificity, later
   rule wins, and the responsive override lost to the base it was overriding.
   Base rules go before the queries that adjust them. */
/* ── the theme switch ─────────────────────────────────────────────────────
   Two segments, mono, in the chrome rather than floating over the world. It
   is app furniture, which is what the Live-surface grammar wants, and it sits
   under the nav on the right where nothing else on the page ever goes. */
.themer {
  position: fixed; z-index: 55; top: calc(var(--nav-h) + 14px); right: 18px;
  display: flex; border: 1px solid var(--line); border-radius: var(--r);
  overflow: hidden; background: rgba(var(--scrim), 0.72); backdrop-filter: blur(10px);
}
.themer button {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.16em;
  text-transform: uppercase; padding: 7px 11px; color: var(--mute);
  transition: background 0.16s var(--ease), color 0.16s var(--ease);
}
@media (hover: hover) and (pointer: fine) { .themer button:hover { color: var(--ink); } }
/* Midnight on Signal, not white on it. This is chrome I invented rather than
   one of the orange bubbles Scotty named, so it is free to clear 4.5 — and it
   badly needed to: white on Signal at 9.5px measured 2.68:1. */
.themer button[aria-pressed="true"] { background: var(--lead); color: var(--onlead); }
body.done .themer { top: auto; bottom: 18px; }


/* ── phone ───────────────────────────────────────────────────────────────
   The world moves to the top third and the reading column takes the rest.
   The hero steps down a rung: a desktop display floor wraps a normal headline
   to six lines at 390px, and that is the type equivalent of a bad crop. */
@media (max-width: 900px) {
  :root { --rail-w: 0px; }
  .rail, .depth { display: none; }

  /* The world owns the top third and the copy owns the rest, so the scrim
     starts BELOW the runway rather than on top of it. Its old 74vh height put
     the dense end of the gradient at 26vh, which is exactly where the terrain
     lives on a phone — the world rendered correctly and was then painted out,
     so every mobile frame was just the quiz on a dark ground. */
  .scrim {
    left: 0; right: 0; width: auto; top: auto; bottom: 0; height: 60vh;
    background: linear-gradient(180deg,
      rgba(var(--scrim), 0) 0%, rgba(var(--scrim), 0.9) 16%, rgba(var(--scrim), 0.985) 34%);
  }
  /* ── THE DEAD BAND ──────────────────────────────────────────────────────
     The world stops around 40vh and the copy column started at 48vh with its
     content centred inside 52vh, so a phone showed roughly 350px of empty
     ground between the mosaic and the question — on the axis where a phone has
     the least to spare, and dead whitespace is the complaint this project
     gets most.

     The column now starts right under the world and anchors its content to the
     TOP of that space rather than the middle, so the question sits directly
     beneath the terrain it is filling in. The gap closes without the world
     shrinking or the type moving. */
  /* THE COPY COLUMN SCROLLS ON A PHONE, and only on a phone. Giving the world
     46vh left 54vh for the questions, and the longest one — ten options for
     "what do you use Claude for" — is taller than that at 390px. It did not
     wrap or clip: it put the Next button below the fold on a page whose body
     is overflow:hidden, so there was no way to reach it at all. The harness
     caught it as "element is outside of the viewport"; a reader would have
     found a question they could not answer.

     Scrolling costs nothing on the questions that fit and is the honest answer
     for the one that does not. `contain` stops the scroll chaining out to the
     body behind it. */
  /* The 96px strip that was reserved at the foot for the reaction line is
     given back to the column now the tips are hidden — it was there so a fixed
     bubble could not land on Back and Next, and there is no bubble. Restore
     `bottom: 96px` alongside `.pop` if the tips return. */
  .deck {
    left: 0; right: 0; width: auto; top: 42vh; bottom: 18px; height: auto;
    align-items: flex-start; padding: 8px 20px 16px;
    overflow-y: auto; overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }
  .q { font-size: clamp(25px, 6.8vw, 32px); }
  .sub, .why { font-size: 14.5px; }
  .opt, .pill { min-height: 44px; }     /* a thumb, not a mouse pointer */
  .cta, .back { min-height: 48px; }
  /* A wrapped CTA is broken, and this one wrapped to two lines at 390px. */
  /* The bar CTA was the heaviest object on a phone screen — a filled block
     wider than the logo beside it, with a hard shadow pushing it into the edge.
     It keeps its 44px hit area and loses the visual weight. */
  .site .cta-nav {
    white-space: nowrap; font-size: 12.5px; padding-inline: 11px;
    box-shadow: 2px 2px 0 var(--btn-shadow);
  }
  .themer button { font-size: 9px; padding-inline: 12px; }

  /* The toggle goes with the world once a phone's report is scrolled. It is
     fixed, so scrolled content passes underneath it — photographed sitting on
     top of the distribution's level names. By the time somebody is reading the
     report they have chosen their ground; the control is still there at the
     top of the page. Tied to the same --world-fade so the two leave together
     rather than one of them hanging on alone. */
  body.done .themer { opacity: var(--world-fade, 1); }
  body.done .themer[style], body.done .themer { transition: opacity 0.2s linear; }

  /* THE REACTION LINE GOES FULL-WIDTH AT THE FOOT. Pinned bottom-right at a
     fixed size it landed squarely on Back and Next — the two controls the
     reader needs to leave the question. Full width along the bottom edge, with
     the scrolling column given room to clear it. */

  /* THE GRADIENT HAS TO CLEAR THE PORTRAIT, and its first version did not: it
     started fading in at 0% while the flattened grid sits between 11% and 41%
     of the viewport, so the bottom three rows of the reader's own answers were
     painted out under the copy — the automation and tools rows, which are the
     two that decide their level. Transparent until the grid ends, opaque a
     few percent later. */
  .results {
    width: auto; right: 0; padding: 48vh 20px 72px;
    background: linear-gradient(180deg,
      rgba(var(--scrim), 0) 0%, rgba(var(--scrim), 0) 45%,
      rgba(var(--scrim), 0.88) 52%, rgba(var(--scrim), 0.995) 59%);
  }

  /* The ported results page stacks: the skill shot goes above its copy rather
     than beside it, and the two-button rows go full width so neither label
     wraps at 390px. */
  :root { --report-w: 100vw; }
  .divider { display: none; }   /* one column on a phone; nothing to divide */
  .results .block { margin-top: 26px; padding-top: 18px; }
  .rhead { margin-top: 26px; }
  .skillrow { grid-template-columns: 1fr; gap: 16px; }
  .skillshot { width: min(180px, 52vw); }
  .ractions { gap: 10px; }
  .bsolid, .bghost { flex: 1 1 100%; }
  .dist { height: 118px; }
  /* THE TOGGLE GOES BACK TO THE TOP ON A PHONE, and this was a blocking bug,
     not a nicety. Pinned bottom-right it sat directly on top of the fourth
     choice on Q1 and swallowed the tap — Playwright refused to click "Claude
     Code" for eight seconds straight because `.themer` was intercepting
     pointer events. A real thumb would have had exactly the same problem and
     no error message.

     Top-right is safe in every state because the top third of a phone screen
     is the world, which is a canvas nobody taps. That is also why it must NOT
     take the body.done bottom position here. */
  /* TOP-LEFT ON A PHONE. Top-right put it straight on the portrait's row
     labels, which hang off the right edge of the grid — and bottom-right, its
     first home, put it on top of an answer button. The left gutter above the
     copy is the one corner nothing else claims in any state. */
  /* Inside the drawer it is a full-width action at the foot of the list, not a
     36px pill floating in a column of 44px rows. */
  .site .links .cta-nav {
    margin: 10px 0 2px; width: 100%; height: 48px;
    justify-content: center; font-size: 15px;
    box-shadow: 3px 3px 0 var(--btn-shadow);
  }

  .themer { top: calc(var(--nav-h) + 10px); bottom: auto; right: auto; left: 12px; }
  body.done .themer { top: calc(var(--nav-h) + 10px); bottom: auto; right: auto; left: 12px; }

  /* ⚠️ EXCEPT ON THE OPENER, WHERE THE TITLE NOW LIVES THERE.
     2026-08-28 (Scotty): "the light and dark toggle on mobile covers where it
     says Claude Assessment." Measured before touching it: the toggle sits
     12,84 → 140,130 and the title 24,94 → 292,132, so it lands 116x36px right
     on top of the words, at 360, 390 and 430 alike.

     This is a collision the toggle could not have anticipated — its left-hand
     home predates the page title by a day, and the two notes above record why
     the other three corners were rejected: bottom-right sat on an answer
     button, and top-right sat on the portrait's row labels.

     MOVING IT RIGHT WAS TRIED FIRST AND STILL COLLIDED. The title runs 268px
     at its 24px phone minimum and the toggle is 128px; on a 390px screen those
     cannot share a row whatever the alignment, and the measurement said so —
     41px of overlap at 390, 71px at 360. Sizing the title down to fit beside it
     would undo the thing Scotty asked for the day before ("much bigger of a
     header") to make room for a control.

     So the toggle drops BELOW the title instead, keeping its left-hand gutter.
     The opener's deck begins around y=380 on a 390x844 screen and the title
     ends near 132, so the band between them is empty in the one state this
     applies to. */
  body[data-screen="opener"] .themer {
    left: 12px; right: auto; top: calc(var(--nav-h) + 66px); bottom: auto;
  }

  /* ── TAP TARGETS ────────────────────────────────────────────────────────
     Audited at 390x844 and four controls came back under the 44px floor. The
     brand's own button file already applies `min-height: 44px` on coarse
     pointers and calls the bar CTA "the one control on every page that must
     not be a near-miss" — but assessment.css pins .cta-nav to a literal 36px
     height, which wins, so the rule never reached it here.

     Fixed by growing the HIT AREA, not the ink. The theme segments and the
     share link keep their type size; they gain padding, or a transparent
     ::after that stretches the target to 44 without moving anything on
     screen. Growing 9.5px mono type to fill 44px would have been a different
     design to fix an ergonomics bug. */
  .themer button { min-height: 44px; padding-inline: 16px; }
  .site .cta-nav { min-height: 44px; height: auto; padding-block: 10px; }
  .site > a:first-child { display: inline-flex; align-items: center; min-height: 44px; }

  .sharelink { position: relative; }
  .sharelink a { display: inline-block; padding-block: 12px; }

  /* The labels stay. Hiding them left the portrait as an abstract block of
     squares on the one screen whose entire job is to be readable. */
  .legend { font-size: 8.5px; letter-spacing: 0.12em; }
  .legend .row { padding-left: 10px; }
  /* The caption goes. Under the grid it landed on the gate headline, and there
     is no room above it either — the six row labels already say what the
     picture is, so the sentence is the part a phone can afford to lose. */
  .legend .cap { display: none; }
}

/* ══ THE FOUR ENTRANCES ═══════════════════════════════════════════════════
   Four genuinely different ways for the report to arrive, switchable from the
   preview panel. They are not four speeds of one animation — each one has a
   different idea about what a result page should feel like, which is the only
   way a comparison tells you anything.

   CASCADE is the default and lives in the rules above: sections rise and fade
   in sequence, each rule drawing in with its section, the verdict wiping up.

   Everything below overrides that. `body[data-anim]` is only ever set in
   preview mode, so the shipped page runs cascade and none of this applies. */

/* ── WIPE · one continuous pass ────────────────────────────────────────────
   Nothing rises. The whole column is revealed left to right, as though a hand
   were drawing a sheet off it, and the rules go instantly because they are
   part of the sheet rather than events of their own. Faster and flatter; reads
   as a document being uncovered rather than assembled. */
body[data-anim="wipe"] .results .seq {
  animation: wipe-in 0.5s cubic-bezier(0.2, 0, 0, 1) both;
  animation-delay: calc(0.1s + var(--i, 0) * 0.09s);
}
@keyframes wipe-in {
  from { opacity: 1; clip-path: inset(0 100% 0 0); }
  to   { opacity: 1; clip-path: inset(0 -2% 0 0); }
}
body[data-anim="wipe"] .results .rhead h1 { animation-duration: 0.5s; animation-delay: 0.14s; }
body[data-anim="wipe"] .results .block::before { animation-duration: 0.4s; }

/* ── FOCUS · it comes into focus ───────────────────────────────────────────
   Everything is already in place and resolves from soft to sharp. No travel at
   all, which makes it the quietest of the four — the page was always there and
   the reader's eye is what arrives. Blur is expensive to animate, so this is
   the one style that is deliberately short. */
body[data-anim="focus"] .results .seq {
  animation: focus-in 0.55s var(--ease) both;
  animation-delay: calc(0.08s + var(--i, 0) * 0.1s);
}
@keyframes focus-in {
  from { opacity: 0; filter: blur(9px); transform: scale(0.985); }
  to   { opacity: 1; filter: blur(0); transform: none; }
}
body[data-anim="focus"] .results .rhead h1 {
  animation: focus-in 0.6s var(--ease) both; animation-delay: 0.18s; clip-path: none;
}
body[data-anim="focus"] .results .block::before { animation-delay: calc(0.1s + var(--i, 0) * 0.1s); }

/* ── CASCADE · the quicker one ─────────────────────────────────────────────
   What shipped before Hold was chosen. Half the gap and two thirds the
   duration: the same moves, delivered rather than presented. Kept as a preview
   option because it is the honest comparison for "is Hold too slow". */
body[data-anim="cascade"] .results .seq {
  animation-duration: 0.62s;
  animation-delay: calc(0.12s + var(--i, 0) * 0.13s);
  animation-timing-function: var(--ease);
}
body[data-anim="cascade"] .results .rhead h1 { animation-duration: 0.72s; animation-delay: 0.26s; }
body[data-anim="cascade"] .results .block::before {
  animation-duration: 0.7s; animation-delay: calc(0.16s + var(--i, 0) * 0.13s);
}
body[data-anim="cascade"] .dcol .dbar { transition-duration: 0.62s, 0.42s; }

/* ══ THE PREVIEW PANEL ════════════════════════════════════════════════════
   A dev surface and dressed as one: mono, small, cornered, and clearly not part
   of the page. It only exists when ?preview is in the URL. */
.prev {
  position: fixed; left: 16px; bottom: 16px; z-index: 90;
  display: flex; flex-direction: column; gap: 8px;
  padding: 12px 14px 14px; border: 1px solid var(--line);
  background: rgba(var(--scrim), 0.94); backdrop-filter: blur(12px);
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.1em;
}
.prev p {
  margin: 0 0 2px; font-size: 9px; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--mute);
}
.prev-row { display: flex; gap: 4px; }
.prev-row button {
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.08em;
  padding: 6px 10px; border: 1px solid var(--line); color: var(--text2);
  min-height: 30px;
}
.prev-row button[aria-pressed="true"] { background: var(--lead); color: #fff; border-color: var(--lead); }
.prev-go {
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.12em;
  text-transform: uppercase; padding: 8px 10px; margin-top: 2px;
  background: var(--ink); color: var(--paper); min-height: 34px;
}
/* The rail and the question column have nothing to say in preview. */
body.preview .rail, body.preview .deck, body.preview .kbd, body.preview .depth { display: none; }

/* ── reduced motion ───────────────────────────────────────────────────────
   Fewer and gentler, not zero. The world still lights, still flattens and
   still tells the reader what their answers made; it simply arrives instead of
   travelling. The opacity that carries the meaning stays. */
/* Reduced motion gets the colour change and the shadow collapse, and no
   movement — the state stays legible without any transform. Straight out of
   the brand file. */
@media (prefers-reduced-motion: reduce) {
  .opt, .pill, .cta, .back, .bsolid, .bghost {
    transition: box-shadow 0.14s, background 0.18s, color 0.18s, border-color 0.14s;
  }
  .opt:hover, .pill:hover, .opt:active, .pill:active,
  .cta:hover, .cta:active, .back:hover, .back:active,
  .bsolid:hover, .bsolid:active, .bghost:hover, .bghost:active,
  .opt:focus-visible, .pill:focus-visible { transform: none; }
  /* Reduced motion keeps the opacity that carries comprehension and drops the
     movement — the report still arrives in order, it just does not travel. */
  /* Reduced motion keeps every arrival and drops every movement: the sections
     fade, the rules and the counters simply are, and nothing travels. */
  .results .seq { animation: seq-fade 0.3s linear both; animation-delay: calc(var(--i, 0) * 0.04s); }
  .results .rhead h1 { animation: seq-fade 0.3s linear both; animation-delay: 0.1s; }
  .results .block::before { animation: none; transform: none; }
  .chips i, .chip-next { animation: seq-fade 0.3s linear both; }
  .dbar { transition: none; }
  /* every preview style collapses to the same plain fade */
  body[data-anim] .results .seq, body[data-anim] .results .rhead h1 {
    animation: seq-fade 0.3s linear both; filter: none; clip-path: none;
  }
  .screen { animation: none; }
  .legend { transition: opacity 0.01s linear; }
  .bsolid:hover, .bghost:hover { transform: none; }
}
