/* NIX — /business-services, version 3: the ladder
   ══════════════════════════════════════════════════════════════════════════
   Copied from services-v2.css and cut down to what v3 draws. The v2 argument
   needed a three-slide frame, an endless app wall, an industry dial and a set
   of plan cards built out of Odoo apps; none of those are on this page, so
   none of their rules are in this file. What is left is the furniture the
   story still stands on — the tour dock, the hero and its orbit, the pinned
   creed, the reveal, the lead form and the settle drawing — plus the three
   things this version invented: the ladder, the method and the packages.

   The hero itself is only PART here. /products opens the same way, so the
   shared furniture (.page--flow, .landing__hero, .reveal) lives in
   components.css; this sheet has the last word about its own page.
   ══════════════════════════════════════════════════════════════════════════ */

/* ---- Voice: the guided-tour dock -------------------------------------- */
.voice {
  position: fixed;
  left: 50%;
  bottom: 20px;
  z-index: var(--z-over);
  display: flex;
  align-items: center;
  gap: 14px;
  width: min(520px, calc(100vw - 32px));
  padding: 12px 12px 12px 22px;
  background: var(--nix-surface);
  border-radius: var(--radius-pill);
  box-shadow: 0 2px 10px rgba(26, 26, 26, 0.16);
  opacity: 0;
  transform: translateX(-50%) translateY(14px);
  transition: opacity 450ms var(--ease-out-expo),
              transform 450ms var(--ease-out-expo);
}

.voice.is-on {
  opacity: 1;
  transform: translateX(-50%);
}

.voice__wave {
  flex: 1;
  min-width: 0;
  height: 36px;
}

.voice__led {
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--focus-ink);
  animation: voice-led 2s ease-in-out infinite;
}

@keyframes voice-led {
0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}


.voice__back {
  flex: none;
  padding: 10px 24px;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--nix-ink);
  color: var(--nix-paper);
  font-family: var(--font-sans);
  font-size: 13px;
  cursor: pointer;
}

.voice__back:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* The section being read carries an orange rule in the margin */
.is-reading {
  position: relative;
}

.is-reading::after {
  content: "";
  position: absolute;
  left: -28px;
  top: 12px;
  bottom: 12px;
  width: 2px;
  background: var(--focus-ink);
}

/* the closing band clips its margins and owns a ::before ember —
   dock the rule inside it */
.cta-band.is-reading::after {
  left: 14px;
  top: 20px;
  bottom: 20px;
}

@media (prefers-reduced-motion: reduce) {
.voice__led {
    animation: none;
  }
}


@media (max-width: 720px) {
.is-reading::after {
    left: -12px;
  }
}


/* ---- Landing (manual mode) -------------------------------------------- */

/* body copy: a centred column with both edges flush, not ragged */

/* Hero */

/* the two auto margins split the free space: text block floats centred
   between the header and the half-buried orbit */

/* only the top arc of the orbit shows; the rest dissolves at the fold.
   Full bleed: the outer ring spans the viewport, capped on huge screens. */
.orbit-well {
  --size: min(1240px, calc(100vw - 72px));
  margin-top: auto;
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  /* never taller than half the orbit, never deeper than half the screen */
  height: min(calc(var(--size) / 2 + 32px), 48svh);
  overflow: hidden;
  pointer-events: none;
  -webkit-mask-image: linear-gradient(to bottom, #000 55%, transparent 96%);
  mask-image: linear-gradient(to bottom, #000 55%, transparent 96%);
}

/* Visual: the ERP constellation — apps wired into one hub */

/* ---- Appverse: infinite canvas of Odoo apps --------------------------- */
.appverse {
  position: fixed;
  inset: 0;
  z-index: 20;
  background: var(--nix-paper);
  opacity: 0;
  transition: opacity 350ms var(--ease-out-expo);
}

.appverse.is-on {
  opacity: 1;
}


/* The bar is FIXED, not sticky.
   Sticky only holds if every ancestor keeps overflow:visible and the browser
   agrees where the scrollport begins. On iOS Safari — collapsing toolbar, notch,
   translucent status bar — neither is dependable, and the page kept climbing
   over the bar and showing through the strip above it. Fixed answers to the
   viewport and to nothing else: no ancestor can take it hostage.
   It costs us the space it used to occupy, which is given back below. */

/* No overpaint here, and there must never be one again: the bar cannot paint
   above its own top edge on an iPhone. A 200px slab of paper was tried and the
   device clipped all but the 3px that fell inside the layout viewport. Nothing
   anchored to that viewport can reach the band under the status bar — the cure
   is that the document no longer scrolls, so nothing is up there to cover.
   See the note on html. */

/* out of flow — the story starts below the bar. The height is measured, not
   guessed: change the logo or the padding and this still lines up.

   ZERO WHEN THERE IS NO BAR. AB01 lost its header when it became a panel
   beside the rail, and the script that measures --head-h returns early with no
   .site-head to measure — so this fell back to the 82px default and the page
   opened with a band of nothing above the mark. The rule now only applies
   where a header actually exists. */

/* Content has to dissolve into the bar, not be guillotined by it. The AI Native
   card is white with a 20px radius: sliding under a hard edge, its rounded
   bottom read as a white slab bolted to the header. This fades the paper down
   over whatever passes beneath. */

/* Hero: headline words rise out of a mask, one by one */

/* Hero orbit: the Odoo catalogue circling the NIX mark on two rings */
.orbit {
  /* --size inherits from .orbit-well */
  position: relative;
  width: var(--size, 640px);
  height: var(--size, 640px);
  /* satellites overhang the box by half a chip — the top margin absorbs it */
  margin: 32px auto 0;
}

.orbit__ring {
  position: absolute;
  border: 1px solid var(--nix-rule);
  border-radius: 50%;
}

.orbit__ring--outer { inset: 0; }

.orbit__ring--inner { inset: 19%; }

 /* inner orbit radius = 31% of size */

/* satellites ride the rings; counter-rotation keeps the icons upright */
.orbit__sat {
  position: absolute;
  top: 50%;
  left: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--nix-surface);
  /* realistic: tight contact shadow + falling ambient shadow */
  box-shadow: 0 1px 1px rgba(26, 26, 26, 0.06),
              0 4px 8px -2px rgba(26, 26, 26, 0.08),
              0 14px 28px -10px rgba(26, 26, 26, 0.14);
  will-change: transform;
}

.orbit__sat--outer {
  --r: calc(var(--size) / 2);
  width: 64px;
  height: 64px;
  margin: -32px 0 0 -32px;
  border-radius: var(--radius-xl);
  animation: orbit-cw 90s linear infinite;
}

.orbit__sat--inner {
  --r: calc(var(--size) * 0.31);
  width: 54px;
  height: 54px;
  margin: -27px 0 0 -27px;
  border-radius: 17px;
  animation: orbit-ccw 60s linear infinite;
}

.orbit__sat img {
  width: 58%;
  height: 58%;
  display: block;
}

/* the brand dot out for a walk on the outer ring */
.orbit__spark {
  position: absolute;
  top: 50%;
  left: 50%;
  --r: calc(var(--size) / 2);
  --a: 140deg;
  width: 8px;
  height: 8px;
  margin: -4px 0 0 -4px;
  border-radius: 50%;
  background: var(--focus-ink);
  box-shadow: 0 1px 4px rgba(26, 26, 26, 0.25);
  animation: orbit-cw 36s linear infinite;
}

/* EVERY HEADLINE ENTERS LIKE THE HERO'S: the words rise out of a mask rather
   than fading up. The hero gets that from a script that wraps each word in a
   box with the word inside it, and only the hero — section headlines are cut
   into words by a different module, one span per word with no inner box, so
   there is nothing to hide the word behind.

   clip-path does the hiding instead. The span is clipped to nothing above its
   own line and slides up into view as the clip opens, which is the same
   gesture with one element instead of two — and it needs no change to either
   script, so page_home keeps the fade it has always had.

   The stagger is the line index the module already writes, so a three-line
   headline still arrives a line at a time. The word no longer fades: a mask
   that also dissolves reads as a smudge. */
.landing__block .landing__headline .lw {
  opacity: 1;
}


/* A HEADLINE IS A LINE OF TYPE, NOT A RULER. Left to itself it runs the full
   width of the panel — 1700px of a single sentence on a wide monitor, which
   the eye has to track across like a scoreboard. 22ch keeps it to the two or
   three lines it was written as, and centres it in the column the paragraph
   underneath already uses.

   ch rather than px because the measure that matters is characters per line,
   and this page's type scales with the window: at a fixed pixel width the
   line count would change every time the font did. */
.landing__block .landing__headline {
  /* THE CREED'S SIZE, NOT THE SHARED HEADLINE'S. components.css sets every
     .landing__headline at clamp(26px, 4vw, 40px), which was written when this
     page still opened on a pinned creed a size above it. Flat, the two stand
     one after the other in the same column and the step down read as the
     argument losing its voice halfway through — so the ladder's heading is set
     at the creed's own clamp and the page speaks at one size throughout. */
  font-size: clamp(28px, 4.6vw, 46px);
  max-width: 22ch;
  /* 22ch IS RIGHT FOR THE LADDER AND WRONG FOR A QUESTION. It keeps a long
     sentence to two or three lines, which is what the road's heading wanted;
     "What would you like to automate?" is thirty-two characters and breaks in
     two under it, over a button, on a screen with nothing else on it. The
     diagnostic's heading is one question and it gets the room to be one line —
     the rule is at the foot of this section, beside the rest of #level. */
  margin-inline: auto;
  text-wrap: pretty;
}


/* ── The creed: four sentences, one screen each ───────────────────────────
   A tall section with a stage pinned inside it. The page scrolls; the stage
   does not; the sentence on it is relieved by the next one, and the one that
   just left stays dimmed above where it can still be read out of the corner
   of the eye.

   EVERYTHING HERE HANGS OFF .is-live, WHICH ONLY nix.js ADDS. Without the
   script — or with prefers-reduced-motion, where the script does not ask for
   it — the stage is a plain column of four paragraphs and nothing is hidden.
   The alternative is a section whose three last sentences exist at opacity 0
   for a reader who never gets the class, which is how a page loses three
   quarters of an argument to one failed request.

   The height is one screen per line: the stage is stuck for exactly as long
   as there are lines to relieve, and the section ends the moment the last one
   has had its screen. --creed-screen is the panel's height rather than the
   window's — .page is the scroller and it is inset by the shell's gap on both
   ends, so 100svh here would overshoot by exactly that and leave the last
   line short of the fold. */
.creed {
  position: relative;
  --creed-screen: calc(100svh - 2 * var(--shell-gap));
  /* one screen per sentence, and the markup carries four: three that make the
     argument and the one that hands it to the drawing */
  --creed-lines: 4;
  /* HOW MUCH SCROLL ONE SENTENCE COSTS, taken from the site this sequence is
     modelled on: morningside.ai gives its text sequence 450vh for five stops,
     which is nine tenths of a screen each against the whole screen we started
     with. Six tenths was tried in between and read as hurried. */
  --creed-step: calc(var(--creed-screen) * 0.9);
  scroll-margin-top: 88px;
}

.creed__stage {
  display: grid;
  /* The flat state — no script, or reduced motion — is a readable column, so
     it keeps a block's worth of air between sentences. The pinned relay
     overrides the gap to zero, since there every sentence stands in the same
     cell. */
  gap: var(--space-8);
  justify-items: center;
  text-align: center;
}

.creed__line {
  /* a step above the block headline: this is the page thinking out loud */
  font-size: clamp(28px, 4.6vw, 46px);
  font-weight: 400;
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: var(--nix-text);
  max-width: 22ch;
  margin: 0;
}

.creed.is-live {
  height: calc(var(--creed-lines) * var(--creed-step));
}

.creed.is-live .creed__stage {
  position: sticky;
  top: 0;
  height: var(--creed-screen);
  align-content: center;
  /* EACH SENTENCE IS ITS OWN HEIGHT, CENTRED IN THE CELL. Grid items stretch
     by default, which is what put a two-line sentence's text at the top of a
     three-line box and made the spacing above the live line disagree with the
     spacing below it. Centring here does that job from the grid, where it
     costs nothing; doing it on the line itself — display:flex — turned the
     line into a flex container, and the moment a sentence carried anything
     but text (the three logos) its words and that thing became columns. */
  align-items: center;
  gap: 0;
  /* NOTHING BEHIND IT WHILE IT IS UP. The stage is stuck over a box nearly four
     screens tall, and what shares the window with it is whatever the page has
     scrolled to underneath — the road arriving under a sentence that has not
     finished being read.

     --nix-panel, WHICH IS WHAT .page IS PAINTED IN (shell.css). It was
     --nix-paper, which is the ground BEHIND the panel: a lighter rectangle
     with hard edges sitting in the middle of the section, which is not "you
     cannot see through this", it is a second colour. The stage has to be the
     exact ground it covers or it is not invisible, it is a panel. */
  background: var(--nix-panel);
}

/* stacked, not stackable: every line takes the same cell, so the relay is a
   crossfade in place and nothing below it moves while it happens.

   AND THE TEXT SITS IN THE MIDDLE OF THAT CELL, which is the whole reason the
   spacing above and below the live sentence used to disagree. Grid items
   stretch: every line's BOX is as tall as the tallest sentence, and with the
   text at the top of its box a two-line sentence carried two lines of empty
   space underneath it. Moving that box up by its own height therefore opened
   a gap of the sentence plus its emptiness above, against a gap of the
   sentence alone below — measured at 171px against 29px. Centred, both sides
   are the same offset less half of each sentence. */
/* ---- The relay, as the app's first run does it --------------------------
   Agent Base's wizard does not swap one step for another: the step you leave
   RECEDES — it drifts up and back, smaller, blurred and faded, and stays there
   as the thing you have already done — while the new one arrives from just
   above its resting place. That is the effect this page is adopting, and the
   numbers below are the wizard's own (styles.css, ".wizard .step.is-past"):
   0.28 opacity, 6px of blur, nine per cent smaller per level of depth, over
   600ms of the same ease.

   The four sentences share one grid area, so the cell is as tall as the
   longest of them and nothing jumps when a short sentence follows a long one.
   The scroll is the Next button. */
.creed.is-live .creed__line {
  grid-area: 1 / 1;
  opacity: 0;
  visibility: hidden;
  transform-origin: center center;
  transition:
    transform 600ms var(--ease-out-expo),
    filter 600ms var(--ease-out-expo),
    opacity 600ms var(--ease-out-expo),
    visibility 600ms;
}

.creed.is-live .creed__line.is-now {
  opacity: 1;
  visibility: visible;
  filter: none;
  transform: none;
}

/* THE SENTENCE JUST LEFT, PUSHED INTO THE BACKGROUND. Up and away, out of
   focus, still legible as something that was said — the wizard's ghost of a
   finished step. */
.creed.is-live .creed__line.is-past {
  visibility: visible;
  opacity: 0.28;
  filter: blur(6px);
  transform: translateY(-24%) scale(0.91);
}

/* the one still to come waits just below its seat and arrives into it, the way
   a wizard step's children drift down into place */
.creed.is-live .creed__line.is-next {
  visibility: visible;
  opacity: 0;
  transform: translateY(14px);
}

@media (prefers-reduced-motion: reduce) {
  .creed.is-live .creed__line {
    transition: none;
    filter: none;
  }

  .creed.is-live .creed__line.is-past,
  .creed.is-live .creed__line.is-next {
    transform: none;
  }
}

@media (max-width: 720px) {
.creed__line { max-width: 18ch; }
}


/* The settle: one line goes from noise to order — the whole story in one stroke */
.settle {
  width: min(560px, 92vw);
  height: auto;
  margin: var(--space-8) auto 0;
  overflow: visible;
}

.settle__lines path {
  fill: none;
  stroke: url(#settle-grad);
  stroke-width: 1.4;
  stroke-linejoin: round;
}

.settle__g0 { stop-color: var(--nix-rule); }

.settle__g1 { stop-color: #00A09D; }

.settle__dots circle {
  fill: #00A09D;
}

.settle__stations circle {
  fill: #00A09D;
  stroke: #00A09D;
  stroke-width: 1;
  filter: drop-shadow(0 0 7px rgba(0, 160, 157, 0.55))
          drop-shadow(0 4px 8px rgba(26, 26, 26, 0.1));
}

.settle__labels text {
  font-family: var(--font-sans);
  font-size: 10px;
  letter-spacing: 0.12em;
  fill: var(--nix-text-soft);
  text-anchor: middle;
}

@media (prefers-reduced-motion: reduce) {
.settle__dots {
    display: none;
  }
}


/* NOTHING ARRIVES ON SCROLL, ON THIS PAGE.
   .reveal is shared furniture: components.css starts it at opacity 0 and
   nix.js switches .is-in on as each block crosses the screen. Every other page
   still does that. Here the whole point is a flat page, so the rule is undone
   for this sheet's own document and every block is simply present — the class
   stays in the markup, because the observer is shared and finding nothing to
   observe is cheaper than a page that has to remember not to write it. */
.reveal,
.reveal.is-in,
.lines.reveal:not(.is-in) {
  opacity: 1;
  filter: none;
  transform: none;
  transition: none;
}

.lines .lw {
  display: inline-block;
}

@media (prefers-reduced-motion: reduce) {
.lines .lw {
    transition: none;
    opacity: 1;
    transform: none;
  }
}


.app-marquee__item {
  position: relative; /* above the wire */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  flex: none;
}

.app-marquee__item img {
  width: 34px;
  height: 34px;
  display: block;
  padding: 2px;
  background: var(--nix-paper);
  border-radius: 9px;
}

.app-marquee__item span {
  font-size: 13px;
  font-weight: 300;
  color: var(--nix-text-soft);
  white-space: nowrap;
}


@media (prefers-reduced-motion: reduce) {
.landing__lead .w__i {
    transform: none;
    transition: none;
  }
}


/* ---- Hero entrance: text first (heading -> intro -> CTAs one by one), then
       the orbit fills in — rings settle, then the app chips ease in smoothly,
       one by one. Keyed to .lead-in so children stay hidden until reveal. --- */

/* CTAs: one button, then the other */

/* orbit reveal, in strict order: outer ring -> outer apps (together) ->
   inner ring -> inner apps (together) -> spark. Pure fades. */
.orbit__ring,
.orbit__sat,
.orbit__spark {
  opacity: 0;
  transition: opacity var(--dur-5) var(--ease-out-expo);
}

.landing.lead-in .orbit__ring--outer { transition-delay: 560ms; }

.landing.lead-in .orbit__sat--outer  { transition-delay: 860ms; }

.landing.lead-in .orbit__ring--inner { transition-delay: 1200ms; }

.landing.lead-in .orbit__sat--inner  { transition-delay: 1500ms; }

.landing.lead-in .orbit__spark { transition-delay: 1980ms; }

.landing.lead-in .orbit__ring,
.landing.lead-in .orbit__sat,
.landing.lead-in .orbit__spark { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
.orbit__ring,
  .orbit__spark,
  .orbit__sat { opacity: 1; transform: none; transition: none; }
}


/* ---- The consultation form (/services) --------------------------------
   Nothing here is a new colour: the fields take --nix-surface the way every
   other card on the site does, and the ring on focus is the ink the page
   writes in. A form that introduces its own palette is the part of a page
   that always looks bought rather than built. */
.lead {
  width: min(680px, 100%);
  margin: var(--space-6) auto 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  text-align: left;
}

/* Two fields per row on a desktop, one on a phone. minmax(0, 1fr) rather
   than 1fr: an input's default width is 20-ish characters and a bare 1fr
   floors at that, which is what pushes a two-column row wider than the
   column it lives in. */
.lead__row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-2);
}

.lead__field { display: flex; flex-direction: column; gap: 6px; }

.lead__label {
  font-size: 12px;
  letter-spacing: 0.04em;
  color: var(--nix-text-soft);
}

/* A SHAPE, NOT A LINE. These fields carried a drawn boundary in
   --field-edge, which is the value mixed to clear the 3:1 WCAG asks of a
   control's edge — and on this page, at this size, four of them in a row read
   as a form printed on a page rather than the site's own. The operator asked
   for the line off, and this is what replaces it: a filled ground a step
   above the paper, which is the same way the app draws its own inputs.

   Said plainly, because it is a trade and not a free win: the fill is under
   the 3:1 a boundary is asked for, so what tells a reader "this is where you
   type" here is the shape and the label above it, not a measured edge. The
   token and the gate that measures it stay exactly as they are for the two
   forms that kept theirs — /beta and the store's search. */
.lead__input {
  width: 100%;
  box-sizing: border-box;
  padding: 13px 16px;
  border-radius: var(--radius-chip);
  border: 0;
  background: var(--chip-tint);
  color: var(--nix-text);
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 1.4;
  transition: background var(--dur-2) ease;
}

.lead__input--area { resize: vertical; min-height: 96px; }

/* NO RING ON THIS ONE. :focus-visible matches a text field on a PLAIN CLICK
   in every engine — the heuristic assumes anything you type into wants the
   indicator — so the site's 2px ring plus its 3px offset drew a double outline
   around a box the reader had just clicked into, which is what it looked
   like: a control with an error state, on a form where nothing is wrong.

   The ground answers instead, on :focus rather than :focus-visible so the
   answer is the same whichever way the field was reached. It is one step,
   deliberately quiet, and it is the only thing left that says which of the
   five fields the keyboard is in — so it may not go away. */
.lead__input:focus,
.lead__input:focus-visible {
  outline: none;
  background: var(--chip-hover);
}

/* "optional", beside the label it belongs to rather than under the field.
   A form that only marks what is required makes the reader count asterisks;
   this one has three required fields out of nine, so the shorter list is the
   one worth writing. */
.lead__hint {
  margin-left: 6px;
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--nix-text-quiet);
}

/* THE TWO MENUS WEAR THE SAME BOX AS THE FIELDS, chevron included. A native
   select is drawn by the operating system — grey, boxed, and nothing like the
   eight controls around it — so appearance:none takes that away and the arrow
   comes back as a background image built from the ink token. Padding on the
   right is what keeps a long option off the chevron. */
.lead__input--pick {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 40px;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 21px) calc(50% + 2px), calc(100% - 16px) calc(50% + 2px);
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  cursor: pointer;
}

/* A fieldset is a box with a border and a legend that floats in it, in every
   engine. Both go: this is a labelled group of controls, drawn the way the
   labelled fields beside it are. */
.lead__set {
  border: 0;
  padding: 0;
  margin: 0;
}

.lead__pills {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-1);
}

/* THE INPUT IS THE STATE AND THE LABEL IS THE CONTROL. The radio itself is
   clipped rather than display:none — a control that is not rendered is one a
   keyboard cannot reach and a screen reader does not announce — and the box
   around it answers :has(:checked), which is what makes the whole pill the
   target. */
.lead__pill {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 13px 16px;
  border-radius: var(--radius-chip);
  background: var(--chip-tint);
  color: var(--nix-text-soft);
  font-size: 15px;
  text-align: center;
  cursor: pointer;
  transition: background var(--dur-2) ease, color var(--dur-2) ease;
}

.lead__pill input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.lead__pill:hover { background: var(--chip-hover); }

.lead__pill:has(:checked) {
  background: var(--nix-ink);
  color: var(--nix-on-ink);
}

/* The ring lands on the pill, because that is the object the keyboard is on;
   the input it wraps is a pixel nobody can see. */
.lead__pill:has(:focus-visible) {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

.lead__send { align-self: flex-start; margin-top: var(--space-1); }

.lead__said {
  margin: 0;
  padding: 12px 16px;
  border-radius: var(--radius-chip);
  background: var(--nix-surface);
  font-size: 14px;
  color: var(--nix-text);
  box-shadow: 0 1px 2px rgba(26, 26, 26, 0.05);
}

.lead__said--bad { color: var(--nix-text-soft); }

/* The answer standing where the form stood. Bigger than a notice, because it
   is the whole content of the section now and not a line above the fields. */
.lead--done .lead__said { padding: 22px 24px; font-size: 16px; }

/* Off screen, still rendered. A bot reads the markup and fills what it finds;
   a field that is display:none or hidden is one the good ones skip. */
.lead__trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}


/* ---- The level diagnostic ------------------------------------------------
   Five questions, five answers each, and the name and the email under them.
   It borrows the lead form's container and its two-up field rows — the same
   page asking the same person for the same two facts should not look like two
   different forms — and adds only what a question needs: a legend the reader
   is answering, and answers that are sentences rather than chips.

   THE ANSWERS ARE ROWS, NOT PILLS. .lead__pill is centred, one line, and sized
   for "Under $5K"; these run to a dozen words and have to be read against each
   other from a common left edge. Stacked and left-aligned, the five read as a
   scale — which is what they are. */
.level {
  width: min(680px, 100%);
  margin: var(--space-6) auto 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  text-align: left;
}

.level__q {
  border: 0;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* THE QUESTION IS THE HEADING, INSIDE THE FORM. The section's own heading asks
   the first one and then stands down — see below — so from the moment the form
   opens there is exactly one question on the screen and it is the biggest thing
   on it.

   IT IS THE LEGEND AND NOT A SHARED LINE REWRITTEN BY SCRIPT, which is what
   makes it travel: the legend is inside the fieldset, so the step machinery
   recedes the question and the answers it belongs to as one thing — blurred
   and pushed back together, arriving together. A shared heading would sit
   still while everything under it moved, and the Spanish overlay would have to
   be taught about a string that lives in no node. */
.level__ask {
  /* OVER THE ANSWERS, NOT BESIDE THEM. Set to the left it read as the first
     item of the list under it, one line of a different weight; centred over
     rows that are all the same width, it is plainly the thing they are answers
     to. width, because a legend shrinks to its text and text-align has nothing
     to work with until it does not.

     AND IT STAYS INSIDE THE FORM'S COLUMN. It was given a measure of its own,
     wider than the 680 the answers stand in, to keep the longest question on
     one line — and a legend wider than its fieldset widens the fieldset, so the
     question and every chip under it were pushed off to the right and the last
     one ran past the edge of the screen. The wrap that was being chased is the
     section's heading, not this: see #level's own rule further down. */
  width: 100%;
  padding: 0 0 var(--space-4);
  font-size: clamp(28px, 4.6vw, 46px);
  font-weight: 400;
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: var(--nix-text);
  text-align: center;
  text-wrap: balance;
}

.level__opt {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 13px 16px;
  border-radius: var(--radius-chip);
  background: var(--chip-tint);
  color: var(--nix-text-soft);
  font-size: 15px;
  line-height: 1.4;
  cursor: pointer;
  transition: background var(--dur-2) ease, color var(--dur-2) ease;
}

/* The radio itself is off, not hidden: a display:none input is one the
   keyboard cannot reach and one a screen reader stops announcing, and the
   whole group would stop being a group. It keeps its box and loses its paint;
   the row around it is what shows the state. */
.level__opt input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.level__opt:hover { background: var(--chip-hover); }

.level__opt:has(:checked) {
  background: var(--nix-ink);
  color: var(--nix-on-ink);
}

/* The ring goes on the ROW, because the input it belongs to is a pixel wide
   and off in the corner of it. */
.level__opt:has(:focus-visible) {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* ---- Picks: the many-answer question ------------------------------------
   Two of the four questions are "choose what applies" rather than "choose one",
   and a stack of full-width rows is the wrong shape for that: eight areas read
   as eight decisions when they are one. A wrap of chips is a set the eye takes
   in at once, and the ticked ones stay visible together — which matters here
   because the verdict names them back.

   The single-answer questions keep .level__opt, the full-width row, because
   there the answers are sentences to be compared against each other. */
.level__picks {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
}

.level__pick {
  position: relative;
  display: inline-flex;
  align-items: center;
  padding: 11px 18px;
  border-radius: var(--radius-pill);
  background: var(--chip-tint);
  color: var(--nix-text-soft);
  font-size: 15px;
  line-height: 1.3;
  cursor: pointer;
  transition: background var(--dur-2) ease, color var(--dur-2) ease;
}

/* The control is off, not hidden: display:none is a control the keyboard
   cannot reach and a screen reader stops announcing, and the group would stop
   being a group. The chip around it is what shows the state. */
.level__pick input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.level__pick:hover { background: var(--chip-hover); }

.level__pick:has(:checked) {
  background: var(--nix-ink);
  color: var(--nix-on-ink);
}

.level__pick:has(:focus-visible) {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* ---- The steps ----------------------------------------------------------
   Everything under this comment only ever applies once the page's own script
   has added .level--stepped. Without it the four blocks stand one under the
   other and the bar holds a single button, which is the form the markup
   describes and the one that works with no script at all. */
.level__bar {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-2);
}

/* WHERE THE READER IS, THEN WHAT THEY CAN DO ABOUT IT — one under the other
   rather than Back and Next at the ends of a rule with the dots between them.
   Read across, that arrangement made the dots look like something separating
   the two controls instead of something the two controls move through. */
.level__acts {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* .lead__send is the contact form's send button and pins itself to the left of
   a column; in this row it is one of two buttons standing side by side. */
.level__acts .lead__send {
  align-self: auto;
  margin-top: 0;
}

/* ONE DOT PER STEP. It said "2 / 4", which is a number the reader has to
   convert into a feeling about how much is left; four dots with one of them
   lit is that feeling directly. It sits between Back and Next and takes the
   space neither wants, so the two buttons stay at the ends of the bar at every
   step — a counter that pushed them around would make the form look like it
   was rearranging itself. */
.level__count {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

/* Three weights of the same mark: the step being answered, the ones behind it,
   and the ones still to come. Not three shapes — a trail of dots that changed
   size as well as ink would be a progress bar pretending to be a row of
   dots. */
.level__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--chip-tint);
}

.level__dot.is-done { background: var(--nix-text-quiet); }

.level__dot.is-on { background: var(--nix-text); }

/* ---- The steps, as the app's own first run does them --------------------
   Lifted from agent-base, styles.css — ".wizard-steps" and the two blocks
   under it — because this is the same act: somebody being asked a short series
   of questions, one at a time, by something that should feel like it is paying
   attention rather than like a form paginating itself.

   TWO MOVEMENTS, AND THE SECOND ONE IS THE POINT. A step that is answered does
   not disappear; it drifts up and back — smaller, blurred, faded to the
   wizard's own 0.28 — and stays there as the thing already done. What arrives
   is the next one, its parts appearing in reading order. Nothing slides
   sideways and nothing is removed, which is why the bar underneath never
   moves.

   ONE CELL. All four share a grid area, so the cell is as tall as the tallest
   of them and the receded steps have somewhere to be. This is also what
   replaced the min-height that used to hold that height by hand. */
.level__steps {
  display: grid;
  grid-template-rows: minmax(0, 1fr);
  width: 100%;
}

.level--stepped .level__q {
  grid-area: 1 / 1;
  visibility: hidden;
  /* CONTAINERS PASS THE POINTER THROUGH. Every step is a full-height box in
     the same cell, so the one on top would eat the current step's clicks;
     only children take hits, which also makes the receded question its own
     click target and leaves the empty space around it inert. */
  pointer-events: none;
  transition:
    transform 600ms var(--ease-out-expo),
    filter 600ms var(--ease-out-expo),
    opacity 600ms var(--ease-out-expo);
  transform-origin: left center;
}

/* ── AND ALL OF IT ONLY WHILE THE FORM IS THE FACE ON SCREEN ─────────────
   These three say visibility: visible and pointer-events: auto INSIDE a form
   that is itself hidden between openings, and a descendant saying "visible"
   overrides an ancestor saying "hidden" — CSS has no inheritance to appeal to
   here. So the current step went on existing behind the closed face: invisible
   because the form is at opacity 0, but painted, hit-testable, and sitting
   exactly on top of "I want to automate…". The button was never dead; the
   press was landing on a question nobody could see.

   Gated on the quiz being open, which is also the honest reading: these are
   steps only while there is a form to step through. Closed, everything inside
   it is hidden — off the tab order too, which the old hidden attribute used to
   do and opacity alone would not. */
body.is-quiz .level--stepped .level__q > * { pointer-events: auto; }

body.is-quiz .level--stepped .level__q.is-current { visibility: visible; }

body.is-quiz .level--stepped .level__q.is-past {
  visibility: visible;
  /* ONE STEP BEHIND, AND NO MORE. Every answered step stayed at 0.28, so by the
     last question there were three ghosts over each other and the screen read
     as a smudge with a question in front of it. The step just left is what
     gives the sense of having moved; the one before that is history.

     Written off --past-d, which the script already sets to how many steps back
     this one now is: the full ghost at one, nothing at two or more. max() and
     not a class, because the value is already here and a second signal for the
     same fact is a second thing to keep in step. */
  opacity: max(0, calc(0.28 - (var(--past-d, 1) - 1) * 0.28));
  filter: blur(6px);
  /* A base shove plus a per-depth one, so the step just left sits behind the
     one being read without touching it. */
  transform:
    translateY(calc(-18% - var(--past-d, 1) * 22%))
    scale(calc(1 - var(--past-d, 1) * 0.09));
  cursor: pointer;
}

body.is-quiz .level--stepped .level__q.is-past > * { cursor: pointer; }

/* THE GHOST IS THE QUESTION, NOT THE ANSWERS. Blurred option rows hanging over
   the current step read as smudge, and a list of choices is not a thing to
   remember — what the reader wants out of the corner of their eye is which
   question they have already answered. */
body.is-quiz .level--stepped .level__q.is-past > :not(.level__ask) { visibility: hidden; }

/* ---- Arrival, top to bottom ---------------------------------------------
   Entering a step, its parts appear in reading order — the question first,
   then the answers one after another. The animation is on the CHILDREN and
   restarts every time a step regains .is-current, so going back gets the same
   welcome as arriving. `both` is what keeps a child invisible until its own
   delay comes up. */
body.is-quiz .level--stepped .level__q.is-current > * {
  animation: level-appear 480ms var(--ease-out-expo) both;
}

body.is-quiz .level--stepped .level__q.is-current > :nth-child(2) { animation-delay: 110ms; }
body.is-quiz .level--stepped .level__q.is-current > :nth-child(3) { animation-delay: 220ms; }
body.is-quiz .level--stepped .level__q.is-current > :nth-child(4) { animation-delay: 330ms; }
body.is-quiz .level--stepped .level__q.is-current > :nth-child(5) { animation-delay: 440ms; }
body.is-quiz .level--stepped .level__q.is-current > :nth-child(n+6) { animation-delay: 550ms; }

@keyframes level-appear {
  from { opacity: 0; transform: translateY(-10px); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .level--stepped .level__q { transition: none; }
  body.is-quiz .level--stepped .level__q.is-current > * { animation: none; }
}

/* THE DIAGNOSTIC'S HEADING IS ONE QUESTION, ON ONE LINE. .landing__block caps
   every heading at 22ch, which is the measure the road's sentence wanted; this
   one is a question over a button on an otherwise empty screen, and a question
   broken in half reads as two. No cap and no balance — there is nothing to
   balance on one line, and the clamp still shrinks it on a window too narrow to
   hold it. */
#level .landing__headline {
  max-width: none;
  text-wrap: nowrap;
}

@media (max-width: 900px) {
  /* a phone cannot hold thirty-two characters at this size on one line, and a
     sentence that will not break is a sentence off the side of the screen */
  #level .landing__headline {
    max-width: 22ch;
    text-wrap: balance;
  }
}

/* ---- The two plans -------------------------------------------------------
   Both cards are the same four steps and the price is the difference in how
   much of the business goes through them, so the cards are the same shape and
   the same weight. A "recommended" one, lifted or outlined, would be the page
   choosing for a reader it has just spent four questions asking.

   Two columns that become one under the tablet breakpoint, where two cards
   side by side are two columns of six characters. */
/* BOTH CARDS, SIDE BY SIDE, AND ONE OF THEM MARKED. Showing one at a time
   behind a "see the other one" made the reader take the recommendation on
   faith and press to check it; two cards let them see what they were not
   given, which is the same information in one glance instead of two. */
.plans {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3);
  width: 100%;
  margin: var(--space-4) auto 0;
  text-align: left;
}

/* THE MARK IS THE LIFT, not a coloured edge — no border on this site may carry
   a colour, and the one that is recommended is the one standing up off the
   page while the other lies flat on it. */
.plans .plan { box-shadow: none; }

.plans .plan.is-it { box-shadow: var(--lift-2); }

.plan {
  display: flex;
  flex-direction: column;
  padding: var(--space-4) var(--space-3);
  border-radius: var(--radius-xl);
  background: var(--nix-surface);
  box-shadow: var(--lift-2);
}

/* icon and name on one row, the way the first version of this page drew them */
.plan__head {
  display: flex;
  align-items: center;
  gap: 11px;
  margin-bottom: var(--space-2);
}

.plan__icon {
  width: 24px;
  height: 24px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.2;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.55;
}

/* the name is a label, the price is the headline: a reader comparing two cards
   is comparing two numbers, and the number should be what the eye lands on */
.plan__name {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--text-2xs);
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--nix-text-soft);
}

.plan__what {
  margin: var(--space-2) 0 0;
  font-size: 15px;
  font-weight: 300;
  line-height: 1.55;
  color: var(--nix-text-soft);
  text-wrap: pretty;
}

/* the line that says the second card contains the first. It is the whole
   reason these two are not alternatives, so it reads at full strength rather
   than as one more grey note. */
.plan__inc {
  margin: var(--space-2) 0 0;
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.02em;
  color: var(--nix-text);
}

/* Foundation's eight, printed: icon box + name, one per row. */
.plan__mods {
  list-style: none;
  padding: 0;
  margin: var(--space-3) 0 var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.plan__mod {
  display: flex;
  align-items: center;
  gap: 11px;
}

.plan__mod-name {
  font-size: 14px;
  font-weight: 400;
  line-height: 1.3;
}

.plan__app {
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  border-radius: 9px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--chip-tint);
}

.plan__app img {
  width: 64%;
  height: 64%;
  display: block;
}

/* Industry's catalogue, on a loop. Two copies of the list stacked; the track
   slides exactly one copy's height and snaps back, so the seam never shows. */
.plan__stream {
  position: relative;
  /* flex-basis 0, NOT auto. With auto the basis is the content height — two
     copies of seventeen rows — so overflow:hidden would clip it while the card
     still grew to that height. Basis 0 means the stream contributes nothing to
     the card's intrinsic height; it fills whatever the grid leaves once the
     taller card has set the row. */
  flex: 1 1 0;
  min-height: 200px;
  overflow: hidden;
  margin: var(--space-3) 0;
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 14%, #000 86%, transparent);
  mask-image: linear-gradient(to bottom, transparent, #000 14%, #000 86%, transparent);
}

.plan__stream-track {
  animation: plan-stream 51s linear infinite;
}

/* margin, not flex gap: the loop slides exactly half the track, and a gap
   between the two copies would be one gap short of that */
.plan__stream-row {
  margin-bottom: var(--space-2);
}

@keyframes plan-stream {
  from { transform: translateY(0); }
  to { transform: translateY(-50%); }
}

@media (prefers-reduced-motion: reduce) {
  .plan__stream-track { animation: none; }
}

.plan__cta {
  display: block;
  margin-top: auto;
  padding: 11px 16px;
  border-radius: 11px;
  text-align: center;
  text-decoration: none;
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 400;
  letter-spacing: 0.01em;
  color: inherit;
  border: 1px solid var(--nix-rule);
  transition: background-color 200ms ease;
}

.plan__cta:hover {
  background: var(--chip-tint);
}

@media (max-width: 900px) {
  /* one under the other: two cards side by side here are two columns of six
     characters, and the loop has no neighbour setting its height any more */
  .plans { grid-template-columns: minmax(0, 1fr); }
  .plan__stream { min-height: 180px; }
}

/* ---- The stop ------------------------------------------------------------
   The first version of this lifted the section out of the flow and hung it
   over the page, which is why it landed like a slap: nothing led up to it, and
   leaving meant putting the reader back by hand because the page underneath
   had changed height while they were away.

   Nothing moves now. The section is one screen tall and stays exactly where it
   is in the document. The reader arrives at it by scrolling; the script stops
   the scroller when its top reaches the top of the window — the page is
   already in the right place, so stopping costs no movement — and the form
   fades up in the cell the button was standing in.

   .level--armed is written by that script and by nothing else. Without it — no
   script, blocked script, a script that threw — the section is its old self:
   the form under the heading, the door still hidden, the same POST. */
.level--armed {
  /* the confetti canvas is placed against this box, and nothing else in the
     section was asking for a coordinate system */
  position: relative;
  /* ONE SCREEN, so that arriving at its top IS arriving at a full view of it.
     .landing__block asks for 80svh, which would leave a fifth of the section
     above the fold at the moment the scroller stops. */
  min-height: 100svh;
}

/* AND WHILE THE PAGE IS STOPPED, THE SECTION IS THE ONE THING THAT SCROLLS.
   Without this the fourth step — two rows of fields under a legend — could
   stand taller than the window on a short one, and with .page locked its send
   button would be somewhere nobody can reach. Capped at exactly the screen it
   already asks for, so the box does not change size when the lock lands.

   `safe center`, for the reason the menu sheet uses it: plain `center` in a
   scroll container puts the top of tall content out of reach, which is the
   same button by another route. */
body.is-quiz .level--armed {
  max-height: 100svh;
  overflow-y: auto;
  overscroll-behavior: contain;
  justify-content: safe center;
}

/* THE TWO FACES, IN ONE CELL. The door and the form are alternatives, and
   putting them in the same grid area is what lets one replace the other
   without the section changing height under a reader who is standing in it.
   The cell is as tall as the form either way, which is also why the button
   sits in the middle of the screen rather than at the top of an empty one. */
.level--armed .level__stage {
  position: relative;
  display: grid;
  width: 100%;
}

/* THE CLOSED FORM TAKES NO ROOM. Every face shares one cell and the cell is as
   tall as the tallest of them — the form, which is past a screen now that its
   questions are headings. Out of flow while it is hidden, the cell is the
   button's own size and the button is in the middle of the screen instead of
   under the bottom of it. It comes back into flow when the quiz opens, which
   is when something has to measure it. */
body:not(.is-quiz) .level--armed .level {
  position: absolute;
  inset: 0;
}

.level--armed .level__stage > * {
  grid-area: 1 / 1;
}

.level--armed .level__open {
  justify-self: center;
  align-self: center;
}

/* ---- Four faces, one cell -----------------------------------------------
   The door, the questions, the beat, and the answer. Only one is ever on, and
   which one is a class on the section: nothing, .level--asking, .level--waiting,
   .level--told. They are all in the same grid area, so the cell stays as tall
   as the questions from first press to last and the heading above it never
   moves — which is the whole reason this section is built this way. */
.level--armed .level__wait,
.level--armed .level__done {
  justify-self: center;
  align-self: center;
  width: min(880px, 100%);
  text-align: center;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(6px);
  transition: opacity 420ms var(--ease-out-expo),
              transform 420ms var(--ease-out-expo),
              visibility 420ms;
}

body.is-quiz .level--waiting .level__wait,
body.is-quiz .level--told .level__done {
  opacity: 1;
  visibility: visible;
  transform: none;
}

body.is-quiz .level--told .level__done { pointer-events: auto; }

/* ── AND THE ANSWER GIVES THE PAGE BACK ──────────────────────────────────
   The stop exists to hold the questions still — one screen, nothing moving,
   the reader answering rather than scrolling. The answer is not that. It is a
   card with eight rows in it and it is taller than a screen, and holding the
   page for something that does not fit on the screen it is being held for is
   the one thing the stop cannot do.

   So the script drops .is-quiz when the verdict lands and the page scrolls
   again, and every one of these says .level--told without asking whether the
   quiz is open. Each is compounded onto .level--armed so it outweighs the
   plain .level--armed rule it is overriding — counted by hand, the same as
   the pair two hundred lines below.

   The form does not need a rule: with .is-quiz gone the base rule already
   hides it, and it is out of flow, so the cell is the answer's own height. */
.level--armed.level--told .level__done {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: none;
}

/* The door would come back the moment .is-quiz went — it is only ever hidden
   by a rule that names the quiz — and a button offering to open a form the
   reader has just finished is a button that undoes their answer. */
.level--armed.level--told .level__open,
.level--armed.level--told .landing__headline {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

/* ONE SCREEN WAS THE POINT WHILE THE QUESTIONS WERE UP. Now the section is as
   tall as what is in it, which is what lets the card be as tall as it needs. */
.level--armed.level--told { min-height: 0; }

/* AND SO DOES THE HEADING. "Now let's see which level your company is on" is
   the sentence that introduces the questions; once they are answered it is
   introducing nothing, and standing over the answer it makes the reader's own
   result the second thing on the screen.

   OPACITY AND VISIBILITY, NEVER display. It keeps its box — that box is the
   reason the questions, the beat and the answer all appear at the same height,
   and dropping it would move the verdict up the screen at the exact moment the
   reader is trying to read it. What goes is the ink, not the space.

   It comes back with everything else when the reader leaves the quiz: these
   are gated on the section being open, so the closed section is its old self,
   heading and door. */
.level--armed .landing__headline {
  transition: opacity 320ms var(--ease-out-expo), visibility 320ms;
}

/* AND IT STANDS DOWN THE MOMENT THE FORM IS UP. It asks the first question,
   which is the only heading a closed section could honestly have; once the form
   is open the step's own legend asks it, and two of the same sentence on one
   screen is what this replaces. It keeps its box — the questions, the beat and
   the answer all appear at the same height because of it, and dropping it would
   move the verdict up at the exact moment somebody is reading it. */
body.is-quiz .level--armed .landing__headline {
  opacity: 0;
  visibility: hidden;
}

@media (prefers-reduced-motion: reduce) {
  .level--armed .landing__headline { transition: none; }
}

/* THE QUESTIONS STEP ASIDE FOR BOTH OF THEM — and the two classes are on the
   same element on purpose.

   Written as `body.is-quiz .level--told .level` this weighs (0,3,1), which is
   exactly what `body.is-quiz .level--armed .level` weighs — the rule two
   hundred lines below that puts the form back — so the later one won and the
   verdict arrived with the whole form still standing on top of it.

   Compounded onto .level--armed it weighs (0,4,1) and wins wherever either of
   them sits in the file. Counted by hand, and written down here because "which
   rule is further down" is not a thing to hold in your head while editing a
   sheet this long. */
body.is-quiz .level--armed.level--waiting .level,
body.is-quiz .level--armed.level--told .level {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

/* ---- The beat -----------------------------------------------------------
   Not a spinner. Three questions take a computer no time at all, and the wait
   is there because being told your own answer the instant you finish giving it
   reads as a lookup rather than as a reading. So it says what it is doing, and
   the three dots under it are the step dots again — the same six pixels, taking
   their turn. */
.level__wait {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
}

.level__waitsay {
  margin: 0;
  font-size: clamp(20px, 2.6vw, 26px);
  font-weight: 400;
  letter-spacing: -0.01em;
  color: var(--nix-text-soft);
}

.level__pulse {
  display: flex;
  gap: 8px;
}

.level__pulse i {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--nix-text-quiet);
  animation: level-pulse 1200ms var(--ease-out-expo) infinite;
}

.level__pulse i:nth-child(2) { animation-delay: 160ms; }
.level__pulse i:nth-child(3) { animation-delay: 320ms; }

@keyframes level-pulse {
  0%, 100% { opacity: 0.25; }
  40%      { opacity: 1; }
}

/* ---- The answer ---------------------------------------------------------
   Six sentences in the markup and one of them carrying .is-it. All six are
   written out because a sentence built in JavaScript is a sentence the Spanish
   sweep never sees — it reads nodes, and a node that did not exist when it ran
   has no entry. */
.level__done {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* Same rule for the plan sentence: both are in the markup so both dictionaries
   carry them, and one is shown. */
.level__done .level__plan { display: none; }

.level__done .level__plan.is-it { display: block; }

/* AND IT ARRIVES IN ORDER. The whole face fading up at once is one large
   rectangle appearing, which is the hard swap this section keeps being rebuilt
   to stop doing: what reads as smooth is the parts landing a beat apart —
   what it is, then what it means, then the mark, then the cards.

   An animation and not a transition, because there is no state to move away
   from: these elements are drawn once when the class arrives, and `both` holds
   them at the last frame instead of snapping back to the first. */
.level--armed.level--told .level__done > * {
  animation: level-rise 520ms var(--ease-out-expo) both;
}

.level--armed.level--told .level__done > *:nth-child(2) { animation-delay: 70ms; }

.level--armed.level--told .level__done > *:nth-child(3) { animation-delay: 140ms; }

.level--armed.level--told .level__done > *:nth-child(4) { animation-delay: 210ms; }

@keyframes level-rise {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .level--armed.level--told .level__done > * { animation: none; }
}

/* THE QUOTE REPLACES THE CARD, it does not appear beside it. A form under a
   card the reader has already chosen is a screen asking them to choose again;
   what they are doing now is filling it in. */
/* NOTHING ELSE ON THE SCREEN. The reader is answering questions again, and
   the rule this section has followed from the first line is that there is one
   question on screen and it is the biggest thing on it. The title, the mark
   and the two cards are what they were reading a moment ago; leaving them up
   makes the form a footnote to a decision already taken. */
.level__done.is-quoting .plans,
.level__done.is-quoting .level__title,
.level__done.is-quoting .level__plan,
.level__done.is-quoting .level__both { display: none; }

.quote {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  width: min(560px, 100%);
  margin: 0 auto;
}

/* ── THE QUOTE STEPS THE SAME WAY, AND IT CANNOT BORROW THOSE RULES ───────
   Every rule that drives the quiz's steps is gated on body.is-quiz — the
   honest reading while the only stepped form on the page was the one the stop
   was holding still. The quote runs after the stop has been handed back, so
   the same handful is written again here against .quote--stepped, which says
   the same thing about this form: it is the face on screen, and its steps are
   steps. Copied and not shared, deliberately: pulling the quiz's rules out
   from behind their gate would change the behaviour of the form this page was
   built around, to save six selectors. */
.quote--stepped .level__q {
  grid-area: 1 / 1;
  visibility: hidden;
  pointer-events: none;
  transition:
    transform 600ms var(--ease-out-expo),
    filter 600ms var(--ease-out-expo),
    opacity 600ms var(--ease-out-expo);
  transform-origin: left center;
}

.quote--stepped .level__q > * { pointer-events: auto; }

.quote--stepped .level__q.is-current { visibility: visible; }

.quote--stepped .level__q.is-past {
  visibility: visible;
  opacity: max(0, calc(0.28 - (var(--past-d, 1) - 1) * 0.28));
  filter: blur(6px);
  transform:
    translateY(calc(-18% - var(--past-d, 1) * 22%))
    scale(calc(1 - var(--past-d, 1) * 0.09));
  cursor: pointer;
}

.quote--stepped .level__q.is-past > * { cursor: pointer; }

.quote--stepped .level__q.is-past > :not(.level__ask) { visibility: hidden; }

.quote--stepped .level__q.is-current > * {
  animation: level-appear 480ms var(--ease-out-expo) both;
}

.quote--stepped .level__q.is-current > :nth-child(2) { animation-delay: 110ms; }

.quote--stepped .level__q.is-current > :nth-child(3) { animation-delay: 220ms; }

.quote--stepped .level__q.is-current > :nth-child(4) { animation-delay: 330ms; }

.quote--stepped .level__q.is-current > :nth-child(5) { animation-delay: 440ms; }

@media (prefers-reduced-motion: reduce) {
  .quote--stepped .level__q { transition: none; }
  .quote--stepped .level__q.is-current > * { animation: none; }
}

/* THE LABEL SITS OVER ITS OWN FIELD, ON THE LEFT. The face is centred — the
   question over it is the biggest thing on the screen and belongs in the
   middle — and the fields inherited that, which put every label over the
   middle of a box the reader reads from the left edge. Text-align, not a
   layout change: the column is the same column. */
.quote__field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  text-align: left;
}

.quote__label {
  font-family: var(--font-sans);
  font-size: var(--text-2xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--nix-text-soft);
}

/* A GROUND, AND NOTHING ELSE. No line around it — a field drawn with one is a
   box on the page rather than a place to write — and no lift either: the
   shadow made every field look pressed out of the paper on a screen where the
   only other thing carrying one is a card. The tint alone says where to type,
   and it says it in both themes, which the lift never did — every lift
   resolves to none in the dark one. */
.quote__field input,
.quote__else {
  width: 100%;
  padding: 12px 14px;
  border: 0;
  border-radius: var(--radius-chip);
  background: var(--chip-tint);
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 400;
  color: var(--nix-text);
}

.quote__field input:focus-visible,
.quote__else:focus-visible {
  outline: 2px solid var(--nix-text);
  outline-offset: 2px;
}

/* The box under "something else", and it only exists once that is the answer.
   Left of the options rather than centred with them: it is a place to type. */
.quote__else {
  margin-top: 8px;
}

/* AND WHAT COMES BACK — over the cards, not instead of them. Clearing the
   plans off the screen made sending a quote look like the end of the
   conversation; they are also still the other half of the offer, and a reader
   who asked about one can ask about the other from here. */
.level__after {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
}

.level__thanks {
  margin: 0;
  font-size: clamp(22px, 3vw, 32px);
  font-weight: 400;
  line-height: 1.25;
  letter-spacing: -0.02em;
  color: var(--nix-text);
  text-wrap: balance;
}

.level__invite {
  margin: 0;
  font-size: 17px;
  font-weight: 300;
  line-height: 1.6;
  color: var(--nix-text-soft);
  text-wrap: pretty;
}

/* A link and not a button: it leaves this page, and everything shaped like a
   button on this screen asks for something instead. */
.level__quick {
  color: var(--nix-text);
  font-size: 15px;
  text-decoration: underline;
  text-underline-offset: 4px;
}

.level__done .level__after { display: none; }

.level--thanked .level__done .level__after { display: flex; }

/* Over the section and out of the way of everything in it: it is thrown when
   the answer lands and it takes itself off screen a few seconds later. */
.level__confetti {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .level__wait,
  .level__done { transition: none; transform: none; }
  .level__pulse i { animation: none; opacity: 1; }
}

/* THE CROSSFADE. visibility rather than display, because display cannot be
   transitioned and a face that vanished on frame one is the hard swap this
   section is being rebuilt to stop doing. pointer-events with it: an invisible
   form still takes clicks, and a button under one that nobody can press is the
   worst version of this. */
.level--armed .level,
.level--armed .level__open {
  transition: opacity 320ms var(--ease-out-expo),
              transform 320ms var(--ease-out-expo),
              visibility 320ms;
}

/* SIX PIXELS, AND NOT ONE MORE. The face that is coming rises the last little
   way into the place the other one was standing; the face that is going settles
   back the same distance. It is small enough that nothing on the page reads as
   having moved — which is the point, because the heading above them does not
   move at all and two things travelling different distances would say they were
   not part of the same block. */
.level--armed .level {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(6px);
}

body.is-quiz .level--armed .level {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: none;
}

body.is-quiz .level--armed .level__open {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(6px);
}

/* AND THE PAGE UNDER IT STOPS. .page is this page's scroller, not the
   document — see the head of base.css — so this is the element that has to be
   told, and telling <body> would do nothing at all. */
body.is-quiz .page { overflow: hidden; }

/* THE SECTION BAR GOES WHILE THE PANEL IS CLOSED. With the panel open the
   reader asked for navigation and should have it; closed, the ring is the only
   control on screen and it means one thing — back. A row of section links
   beside it would offer to leave in three other directions.

   BUT IT KEEPS ITS SPACE. display:none was what moved the heading: the bar is
   sticky, which means it is in the flow and its height is part of everything
   above this section. Taking it out shortened the page above by exactly that,
   and the scroller — already parked at a number computed a frame earlier — was
   suddenly parked past the section's top. The heading went up by the height of
   a bar the reader had just watched disappear, which read as the section
   jumping at the exact moment it should have been standing still.

   Hidden by visibility, so the box stays and nothing above it moves. */
body.is-quiz:not(.is-nav) .nix-sectionbar {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.nix-sectionbar {
  transition: opacity 200ms var(--ease-out-expo), visibility 200ms;
}

@media (prefers-reduced-motion: reduce) {
  .nix-sectionbar { transition: none; }
}

@media (prefers-reduced-motion: reduce) {
  .level--armed .level,
  .level--armed .level__open {
    transition: none;
    transform: none;
  }
}

/* ---- The verdict --------------------------------------------------------
   What the three answers came to, said in the road's own words for that rung.
   Set at the ladder's size rather than the form's: it is the answer the reader
   filled the form to get, and it should not arrive looking like a receipt.

   .level--done is gone with the page it belonged to: the verdict used to be a
   server-rendered block that replaced the form after a reload, and it is a
   face of the stage now — see .level__done above, which is the element and not
   a modifier of the form. */
/* WHAT THE SCREEN IS, said the same way to everybody: two cards are on it,
   and a heading naming one of them argues with the other. */
.level__title {
  margin: 0;
  font-size: clamp(28px, 4.4vw, 46px);
  font-weight: 400;
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: var(--nix-text);
  text-wrap: balance;
}

.level__both {
  margin: 0;
  font-size: 17px;
  font-weight: 300;
  line-height: 1.6;
  color: var(--nix-text-soft);
  text-wrap: pretty;
}

/* AND THE ONE LINE ON THIS SCREEN THAT IS ABOUT THIS READER. A pill, because
   it is a mark and not a title — and not a button, because pressing it would
   do nothing and a control that does nothing is worse than a label. */
.level__plan {
  align-self: center;
  margin: var(--space-2) 0 0;
  padding: 8px 16px;
  border-radius: var(--radius-pill);
  background: var(--nix-ink);
  color: var(--nix-on-ink);
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 400;
  letter-spacing: 0.01em;
}

.level__said {
  margin: 0;
  font-size: 17px;
  font-weight: 300;
  line-height: 1.6;
  color: var(--nix-text-soft);
  text-wrap: pretty;
}

/* the error the form answers with, which is the same element saying something
   much smaller — it belongs beside the fields, not over them */
.level__said--bad {
  padding: 12px 16px;
  border-radius: var(--radius-chip);
  background: var(--nix-surface);
  font-size: 14px;
  letter-spacing: 0;
  text-align: left;
}

@media (max-width: 640px) {
.orbit__sat--outer {
    width: 50px;
    height: 50px;
    margin: -25px 0 0 -25px;
    border-radius: var(--radius-lg);
  }
  .orbit__sat--inner {
    width: 44px;
    height: 44px;
    margin: -22px 0 0 -22px;
    border-radius: 14px;
  }
}


@media (max-width: 640px) {
.lead__row { grid-template-columns: 1fr; }
  .lead__send { align-self: stretch; text-align: center; }
}


/* ---- Hero lead, two lines ------------------------------------------------
   The lead is written as one span per line so the break is the copy's, not the
   viewport's: "Making business use AI" over "the right way", at every width and
   in both languages. nix.js splits the words inside each span; with reduced
   motion it does not run at all, and these blocks are what still hold the two
   lines apart. */
.landing__lead .lead__ln {
  display: block;
}

/* AND THE HERO IS GIVEN THE ROOM FOR IT. components.css sets every hero to
   880px because that is the measure a heading at 54px wants; at 88px the
   longer of these two lines is within a few characters of it, and a heading
   that wraps its second word onto a third line is the whole reason the size
   was chosen. The column this page is laid out in is 1080px, so 1000 is room
   the page already had. Everything under the heading keeps its own measure —
   the promise is capped at 560px, the buttons and the orbit are centred. */
.landing__hero {
  max-width: 1000px;
}

/* The hero lost its intro paragraph, and the two auto margins split what was
   left evenly — which parked a two-line heading in the middle of the screen,
   far from the header. The lead now takes a measured top margin instead of half
   the free space, so it sits nearer the top and the orbit's own auto margin
   absorbs the rest.

   AND IT IS THE WHOLE HERO NOW. With the eyebrow gone there is one thing to
   read above the promise, so it is set as large as the measure allows rather
   than as one line of several: five words over two lines, at up to 88px.

   THE CEILING IS THE HERO'S OWN WIDTH, not a number that looked right. The
   header is 880px across and the longer of the two lines is eighteen
   characters — at roughly half an em each, 88px is the last step that still
   sets it on one line, and 96 wraps "business" onto a third. The floor is the
   phone's: 32px puts the same eighteen characters inside a 375px screen with
   its gutters. */
.landing__hero .landing__lead {
  margin-top: clamp(14px, 3svh, 34px);
  font-size: clamp(32px, 7.4vw, 88px);
}

/* THE PROMISE UNDER THE HEADING BREAKS EVENLY, AND NEVER ON ONE WORD.
   Two sentences with a <br/> between them, centred, in whatever measure the
   screen gives them. Filled greedily — which is what a browser does by
   default — each one ran to the edge and dropped its remainder onto a line of
   its own: on a phone that left "it is." and then "own." alone in the middle
   of an otherwise empty line, three words with a full line of air around
   them, which reads as a mistake rather than as a paragraph.

   text-wrap: balance asks the browser to even the lines of each sentence out
   instead of filling and spilling, so a two-line sentence breaks near its
   middle. It is a hint, not a layout: where it is not supported the paragraph
   is exactly what it is today. And it is done here rather than in the copy —
   a <br/> placed by hand is a break that is right at one width and wrong at
   every other, and it would have to be right in both dictionaries. */
.landing__hero .landing__text--intro {
  text-wrap: balance;
}

@media (max-width: 720px) {
  /* AND A NOTCH SMALLER ON A PHONE, where 19px against a 400-pixel measure is
     six or seven words a line — short enough that balancing alone still
     leaves a thin last line. At 17px each sentence is two full ones. */
  .landing__hero .landing__text--intro {
    font-size: 17px;
    line-height: 1.5;
  }
}

/* The buttons were reading as part of the heading. They keep their own line of
   air now — a step and a half off the shared spacing, matched to the bigger
   type above them — and stand a little further apart from each other. */
.landing__hero .landing__cta {
  margin-top: clamp(28px, 5svh, 52px);
  gap: var(--space-3);
}

/* ---- The road, drawn as a flow chart -----------------------------------
   Five tiles descending in two columns, joined by right angles, each with the
   panel that explains its phase beside it. The reader is following a route
   from where they are to where the work runs itself; a route has turns, and
   every turn here costs a year of somebody's work, which a bulleted list
   cannot say.

   HOW IT HOLDS TOGETHER. The joins are one SVG path in a fixed 800x2060
   viewBox; the tiles are placed on it in percentages taken from the same
   coordinates. The box keeps that aspect ratio at every width — the moment the
   drawing could stretch on one axis only, the tiles would slide off their own
   line. It also means nothing is measured in JavaScript: no recomputing on
   resize, on a font swap, or when the Spanish text runs a line longer than the
   English.

   THE LINE IS A STROKE, NOT A BORDER. Law 1 forbids a border carrying a
   colour; this is a drawn part rather than the edge of anything, so it is
   painted as an SVG stroke in the rule token. */
.road {
  position: relative;
  width: 100%;
  /* WIDER, BECAUSE THE TYPE GREW. The step's line is set at the creed's size
     now and a 34% column at that size is seven characters wide; the drawing
     takes the room the words need. */
  max-width: 1180px;
  /* AIR UNDER THE SENTENCE. 48px was a gap between a heading and a drawing;
     at the creed's size the heading is three lines of 46px and the drawing
     started where the last one ended, so the first tile read as a fourth
     line of it. A tenth of a screen, floored at the old value so a short
     window does not close it entirely. */
  margin: clamp(24px, 4svh, 56px) auto var(--space-4);
  /* 2060 UNITS TALL. A leg — one stop's centre to the next — was 270 when the
     step's line was set small, and is 450: 207 of straight, an 18-unit corner,
     the crossing, another corner, 207 of straight, so the elbow is still
     centred between the two stops it joins and its radius did not change. Five
     stops and 130 of pad at each end makes the box. The tiles' --y percentages
     are read off the same numbers, which is the only reason they stay on the
     line — they are recomputed whenever a leg changes, never nudged. */
  aspect-ratio: 800 / 1610;
}

.road__line {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.road__stroke {
  stroke: var(--nix-rule);
  /* DRAWN AS YOU ARRIVE. pathLength normalises the whole path to 1, so the
     dash pattern is written in fractions of it and never has to be measured —
     the same two numbers work at any size the box comes out. */
  /* THE WHOLE LINE, ALWAYS. The offset used to be written per frame from the
     reader's scroll; the page is flat, so the drawing is finished before
     anybody looks at it. */
  stroke-dasharray: 1;
  stroke-dashoffset: 0;
}

.road__stops {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* EVERY STOP IS A FULL-WIDTH ROW pinned to its own height on the line. Sized
   to nothing, its panel asked for a percentage of zero and came out one word
   per line; the row spans the road, and the tile and the panel are placed
   along it from the same --x the path was drawn with. */
.stop {
  position: absolute;
  left: 0;
  top: var(--y, 50%);
  width: 100%;
  height: 0;
}

/* the tile: a rounded square with the step's number, standing on the line */
/* THE TILE CARRIES THE WHOLE MARKER: the phase's icon, the number, and the
   word that says what the number is. It is the only object in the drawing now
   — the words beside it stand on the page — so everything that identifies a
   step lives in one square. */
.stop__n {
  position: absolute;
  left: var(--x, 50%);
  top: 0;
  transform: translate(-50%, -50%);
  width: 104px;
  height: 104px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  border-radius: var(--radius-xl);
  background: var(--nix-surface);
  box-shadow: var(--lift-2);
  color: var(--nix-text);
}

.stop__num {
  font-family: var(--font-sans);
  font-size: var(--text-xl);
  line-height: 1;
  color: var(--nix-text);
}

/* the word under the number: what the digit MEANS, so the tile reads as a
   position on the climb rather than as an item in a list */
.stop__lv {
  font-family: var(--font-sans);
  font-size: var(--text-2xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--nix-text-soft);
}

/* THE ICON IS THE PHASE AT A GLANCE. Lucide, inline rather than linked: the
   page has no build step and no icon font, and three of these are on screen at
   once — a request each would be three requests to draw sixty pixels.

   It has no chip of its own any more: it is inside the tile, which is the
   chip. The tint stays, on the stroke, and it is the only colour on the page —
   five markers of identical grey are five things the eye has to read before it
   can tell them apart. */
.stop__icon {
  display: block;
  width: 26px;
  height: 26px;
  margin-bottom: 4px;
  color: var(--tint-ink, var(--nix-text-soft));
}

.stop__icon svg {
  width: 100%;
  height: 100%;
  display: block;
}

/* one pair per phase, in the order they are climbed: the hand work warm, the
   one store teal, the help plum, the approval blue, and the thing that runs
   itself green */
.road__stops > :nth-child(1) { --tint-ink: #98663A; }
.road__stops > :nth-child(2) { --tint-ink: #04716F; }
.road__stops > :nth-child(3) { --tint-ink: #79496D; }
.road__stops > :nth-child(4) { --tint-ink: #33529B; }
.road__stops > :nth-child(5) { --tint-ink: #2F6C45; }

/* Dark keeps the hue and swaps the roles: a pastel ground would be the
   brightest thing on a #101010 page and would read as five lit buttons. */
html[data-theme="dark"] .road__stops > :nth-child(1) { --tint-ink: #D3A469; }
html[data-theme="dark"] .road__stops > :nth-child(2) { --tint-ink: #6FC6C2; }
html[data-theme="dark"] .road__stops > :nth-child(3) { --tint-ink: #C193B7; }
html[data-theme="dark"] .road__stops > :nth-child(4) { --tint-ink: #8FAEE8; }
html[data-theme="dark"] .road__stops > :nth-child(5) { --tint-ink: #7FBE95; }

/* THE WORDS STAND ON THE PAGE, not in a card. Five white boxes beside five
   white tiles was two objects per step and one idea; with the marker carrying
   the icon there is nothing left for a panel to hold but text, and text on
   paper is what the rest of this page is. */
.stop__body {
  position: absolute;
  top: 0;
  transform: translateY(-50%);
  /* the creed's size needs the creed's measure: at 46px a 38% column is nine
     characters, and every step came out four lines deep */
  width: 41%;
  text-align: left;
}

.stop--left .stop__body {
  left: 57%;
}

.stop--right .stop__body {
  right: 57%;
}

/* THE SAME VOICE AS THE CREED, two rungs quieter. That section sets the page's
   thinking-out-loud type — 400 weight, tight tracking, a line and a fifth of
   leading — and these five lines are the same thing said about one step, so
   they are cut from the same cloth rather than styled as list headings. */
/* ---- And what it leaves you able to do ----------------------------------
   THE BIG LINE IS WHAT WE BUILD; THIS IS WHAT THE READER GETS OUT OF IT.
   Both halves have moved twice. The step used to name a condition of the
   business with what we do about it underneath, which was a taxonomy with
   commentary; then the step named the thing we hand over with a "you are here
   when" test underneath, which was a build sequence with a ruler bolted to it.
   The test belonged to the diagnostic, and the diagnostic already has it — so
   what is left under each step is the only thing a reader actually wants from
   a list of work somebody else will do: what they will be able to do once it
   is done.

   Softer ink and two thirds of the size, because it is the same voice
   answering a different question — and because four steps each carrying two
   sentences at 46px is a section nobody finishes. */
.stop__do {
  font-family: var(--font-sans);
  font-size: clamp(15px, 1.5vw, 18px);
  font-weight: 300;
  line-height: 1.55;
  color: var(--nix-text-soft);
  margin: 0 0 12px;
}

/* THE NAME IS A PILL OVER THE HEADING. It has been four things in five
   commits — a line inside the tile, where it fought the number for a 96-pixel
   box; a plain eyebrow; a pill under the description; a pill between the two;
   and now a pill above both. What it always wanted to be is the label on the
   step, and a label goes on the front of the thing it names: first in the
   body, then the sentence, then what the reader gets.

   Same chip as the rest of the page — the surface, the pill radius and the
   control's lift — so a step's name and an answer in the diagnostic are the
   same object at the same size, which they are. */
.stop__name {
  display: inline-block;
  margin: 0 0 14px;
  padding: 4px 12px;
  border-radius: var(--radius-pill);
  background: var(--nix-chip);
  box-shadow: var(--lift-control);
  font-family: var(--font-sans);
  font-size: var(--text-2xs);
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--nix-text-soft);
  white-space: nowrap;
}

.stop__t {
  font-family: var(--font-sans);
  /* ONE SENTENCE PER STEP, at the creed's own size. It was a heading with a
     caption under it, which is two voices for one idea and two sizes to
     choose between; joined into a single line — the phase, then what it
     actually looks like — there is nothing left to rank. */
  font-size: clamp(28px, 4.6vw, 46px);
  font-weight: 400;
  line-height: 1.14;
  letter-spacing: -0.02em;
  color: var(--nix-text);
  margin: 0 0 10px;
}

/* THE HERO'S ENTRANCE, ON EACH STEP. The words are cut into masks by the page
   script (NIX.splitLead, the same call the hero makes) and each one rises out
   of its own box a beat after the one before it. The hero rises when the page
   arrives; a step rises when the road's line reaches it, which is what .is-lit
   already means here. */
.stop__t .w {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
}

.stop__t .w__i {
  display: inline-block;
}




/* ---- Arriving -----------------------------------------------------------
   A stop is switched on by the line reaching it, not by its own row entering
   the screen: the page's script writes .is-lit from the same fraction it
   writes the dash offset with. On a box this tall the two are a screen and a
   half apart, which is how level 2 came to be sitting there lit with nothing
   joining it to level 1.

   The tile zooms in from three quarters of its size and the panel comes up
   behind it. The overshoot in the easing is deliberate and small: a tile that
   lands exactly reads as a fade, one that bounces reads as a toy. */
.stop .stop__n,
.stop .stop__body {
  opacity: 1;
}

.stop .stop__n {
  transform: translate(-50%, -50%);
}

.stop .stop__body {
  transform: translateY(-50%);
}

/* .is-lit is written once, by the page's own one-pass script, and it no longer
   means "the line has reached this one" — every stop carries it. The rules stay
   so the markup and the sheet still agree about what a stop looks like. */
.stop.is-lit .stop__n {
  opacity: 1;
  transform: translate(-50%, -50%);
}

.stop.is-lit .stop__body {
  opacity: 1;
  transform: translateY(-50%);
}

/* the line that lands after the road, and the only sentence on the page set at
   heading scale without being one */
/* A STAIRCASE NEEDS WIDTH TO BE A STAIRCASE. Under 900 the two columns and
   their panels have nowhere to stand, so the drawing goes and the five stops
   become a column with a rail down the tiles — the same route, read as a
   list. */
@media (max-width: 900px) {
  .road {
    aspect-ratio: auto;
    max-width: 560px;
  }

  .road__line {
    display: none;
  }

  .road__stops {
    display: flex;
    flex-direction: column;
    /* ONE LEVEL AT A TIME. Stacked, five steps a --space-4 apart read as a
       single list of sentences with numbers in the margin; at this type size
       each one needs enough air around it to be the only thing being read.
       Two thirds of the tile's own height, and no less than 64px. */
    /* the same opening the drawing got above: a level and its panel need to
       be the only thing being read, and at this type size 64px was not */
    gap: clamp(120px, 22svh, 220px);
    position: relative;
    text-align: left;
  }

  /* THE RAIL THE TILES STAND ON, in place of the drawn line — and it fills as
     you come down it, the same as the drawing does on a wide screen. Two
     layers: the pale track between the first tile's centre and the last, and
     the travelled part over it, as tall as the fraction the page's script
     writes into --ink. */
  .road__stops::before,
  .road__stops::after {
    content: "";
    position: absolute;
    left: 37px;
    top: 38px;
    width: 2px;
    /* BEHIND THE TILES, and it takes saying so. A pseudo-element of the list
       paints as its last child, so both rails were landing on top of the
       numbers — a hard line straight through the middle of every one of
       them. */
    z-index: 0;
  }

  .road__stops::before {
    bottom: 38px;
    background: var(--nix-rule);
  }

  /* the travelled part. --nix-text-quiet rather than the ink: this is the
     drawn line's colour on a wide screen, one step up from the track, not a
     black wire down the page. */
  .road__stops::after {
    height: calc(var(--ink, 0) * (100% - 76px));
    background: var(--nix-text-quiet);
  }

  .stop {
    z-index: 1;
  }

  .stop {
    position: relative;
    left: auto;
    top: auto;
    width: auto;
    height: auto;
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
  }

  .stop__n {
    position: relative;
    left: auto;
    flex: 0 0 auto;
    width: 76px;
    height: 76px;
    transform: none;
  }

  .stop__icon {
    width: 20px;
    height: 20px;
    margin-bottom: 2px;
  }

  .stop__num {
    font-size: var(--text-lg);
  }

  .stop--left .stop__body,
  .stop--right .stop__body {
    position: relative;
    left: auto;
    right: auto;
    top: auto;
    transform: none;
    width: auto;
    flex: 1 1 auto;
  }

  .stop .stop__n {
    transform: scale(0.72);
  }

  .stop .stop__body {
    transform: translateY(18px) scale(0.96);
  }

  .stop.is-lit .stop__n,
  .stop.is-lit .stop__body {
    transform: none;
  }

  /* the focus module is off under 900px, so nothing writes the property and
     the fallback is what a lit stop gets */
  .stop.is-lit .stop__n,
  .stop.is-lit .stop__body {
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .stop .stop__n,
  .stop .stop__body {
    opacity: 1;
    transition: none;
  }

  .stop .stop__n {
    transform: translate(-50%, -50%);
  }

  .stop .stop__body {
    transform: translateY(-50%);
  }

  .road__stroke {
    stroke-dashoffset: 0;
    transition: none;
  }
}

/* ---- Air between sections -----------------------------------------------
   With nothing pinned any more, one section ends exactly where the next
   begins: the creed's last sentence and the ladder's heading were a hundred
   pixels apart, both centred, both at the same size — one long paragraph as
   far as the eye is concerned.

   A section break has to be legible without a rule or a band, and on a page
   this quiet the only thing left to say it with is distance: a fifth of a
   screen between blocks. */
.landing__block {
  margin-bottom: clamp(120px, 20svh, 260px);
}

/* AND THE CREED PAYS FOR ITS OWN AIR — WHEN IT IS FLAT. Every other section
   here is a .landing__block and carries the gap above; the creed is not one,
   and flat it ended one paragraph where the road began the next line, so the
   two read as one long block of centred text. */
.creed {
  margin-bottom: clamp(120px, 20svh, 260px);
}

/* PINNED IT ALREADY HAS THE AIR, AND THEN SOME. The stage releases the page
   exactly as the last sentence finishes, so the gap is the whole of the last
   screen the box was still spending — and a quarter-screen margin under that
   put the road most of a screen further down again. The box is the spacing. */
.creed.is-live {
  margin-bottom: 0;
}

/* AND THE ROAD COMES UP BEHIND THE STAGE.
   ═══════════════════════════════════════════════════════════════════════════
   The gap that was left after all of that is structural, not spacing: the
   stage is a screen tall and the sentence is centred in it, so the lower half
   of the stage is empty by construction — a two-line sentence at 46px leaves
   something like four tenths of a screen of nothing under itself, every time,
   and there is no margin anywhere to remove because none of it is margin.

   So the road starts inside that emptiness. The stage is opaque and stuck, so
   the section climbing up behind it is invisible until the box ends; the
   moment it does, the road is already on the screen rather than a scroll away.
   Which is what "sticky until the road" means: the sentence holds still and
   the drawing arrives to meet it.

   THE NUMBER IS THE EMPTY HALF, not a nudge. --creed-screen is the stage's own
   height and it is declared on .creed, which is a sibling — custom properties
   go down, not across — so the expression is written out rather than borrowed.
   0.35 rather than 0.5: the sentence has to keep the air directly under it, or
   the first tile would be touching the last word at the moment of release.

   Only while it is live, which is only above 900. Flat, the two sections are
   two sections and the gap between them is the margin that belongs there. */
.creed.is-live + #ladder {
  margin-top: calc((100svh - 2 * var(--shell-gap)) * -0.35);
}

/* A SECTION STANDING DOWN. .landing__block and .creed are display:flex/grid,
   which beats the UA rule hidden="hidden" leans on — the same trap the level
   dialog fell into. While the page is rebuilt one section at a time, the
   attribute has to win. */
[hidden] {
  display: none !important;
}

/* NOTHING SCROLLS SIDEWAYS. The scaled planes that used to paint wider than
   their boxes are gone, but the guard stays: it is what caught the horizontal
   scrollbar at the creed-to-road seam once, and it costs nothing. Clipped
   rather than hidden, because `clip` does not turn the element into a scroll
   container. */
.page,
#ladder {
  overflow-x: clip;
}

/* THE ROAD STARTS WHERE THE CREED LETS GO. .landing__block reserves 80svh and
   centres what is in it — which is right for a section that is one sentence,
   and wrong for one whose content is two and a half screens of drawing: the
   reserve is never reached, so all it does is put four tenths of a screen of
   nothing above the first tile. On top of that the drawing carries its own air
   in the box — the first tile's centre sits at 8% of it — and the creed's last
   sentence stays pinned until the moment this section begins. Three lots of
   space for one seam, and the reader saw all three.

   The block's own reserve goes and the drawing's top margin halves. What is
   left above the first tile is the 8% inside the drawing, which is the drawing
   breathing rather than the page. */
#ladder {
  min-height: 0;
}
