/* Angry Keyboard — the public site.
 *
 * THE DESIGN LANGUAGE: bands on one grid, dark anchors, white type on dark,
 * ONE accent used only as rules and blocks and button grounds, stark
 * typography. It began as the pricing flyer's language; the 2026-09-14
 * rebrand kept the structure and re-derived every color from the new mark.
 * Editorial, heavy, certain. Never startup-gradient, never soft SaaS.
 * (The amber flyer itself is off-brand until regenerated — recorded in STATE.)
 *
 * ---------------------------------------------------------------------------
 * THE PALETTE COMES FROM THE LOGO, not from the app, and not from taste.
 *
 * The mark is `angry_keyboard_logo_2_wbpu5c` (the Angry Keyboard lockup,
 * adopted with the 2026-09-21 rename): a navy wordmark, a teal bolt and
 * "KEYBOARD", and teal/violet icon circles, on white. Sampled straight off
 * the file: teal #04A9AD, violet #4C25B8 (the file reads #4F23BE, ΔE 3.8 —
 * agreement), navy #0A1729.
 *
 * THE TEAL WAS RE-RULED ON 2026-09-21 TO FOLLOW IT. The previous teal,
 * #0EB7B6, was sampled from the previous mark (IMG_4651); this file's teal
 * is ΔE 6.1 darker. The logo sits beside teal buttons on every page, and two
 * teals side by side read as a mistake, so the palette moved rather than the
 * logo. #0EB7B6 and the amber before it (#FEC303) are retired, and the tests
 * keep both out of type so neither can creep back.
 *
 * NAVY IS BOTH THE INK AND THE DARK GROUND: it is the wordmark's colour, as
 * the old ink #191D26 was the old wordmark's. Moving the ink was not only
 * doctrine — ink on the derived hover teal was 4.3:1, under the floor; navy
 * on it is 4.6:1.
 *
 * TEAL IS NEVER A TEXT COLOUR — the same law amber lived under, for the same
 * measured reason: ~2.9:1 on white, unreadable as copy. It earns its place
 * as a BLOCK, a RULE, and a BUTTON GROUND, where navy sits on it at ~6.2:1.
 * That is the mark's own pairing rather than an invention, and a test asserts
 * it rather than trusting this comment. Violet clears 9.2:1 on white and MAY
 * carry type, but is held in reserve — one accent is the voice; a second is
 * noise until a surface genuinely needs it.
 *
 * ---------------------------------------------------------------------------
 * THE ONE-CONTAINER RULE, which is the whole structural fix.
 *
 * Every section is a BAND. A band's BACKGROUND may span the viewport; a band's
 * CONTENT never does. Content lives in `.wrap`, one width, centred, everywhere,
 * with no exceptions.
 *
 * This replaced `section > * { max-width: … ; margin-inline: auto }`, which
 * looked equivalent and was not: any child that reset its own margin escaped
 * the column silently. `.stats { margin: 0 }` did exactly that, and at 2560px
 * the three proof cards sat hard against the left viewport edge while the
 * heading above them sat 900px inward. That is diagnosis #1, and the cause was
 * one shorthand in an unrelated rule. A container you have to opt out of beats
 * a container you have to remember to opt into.
 */

:root {
  /* ---- ink ---- */
  --ink: #0A1729;          /* the wordmark's own navy, sampled (was #191D26) */
  --ink-soft: #4B515C;
  --muted: #6A717D;
  --white: #FFFFFF;
  --band: #F2F4F6;         /* cool off-white; sits with the teal, not the old cream */
  --line: #DDE1E6;

  /* ---- the dark bands ---- */
  --dark: #0A1729;         /* the logo's navy (was near-black #12141B) */
  --dark-line: #223044;    /* the navy lifted to the old line's ~1.35:1 step */
  /* On dark, body grey must clear 4.5:1. #C7CCD4 on #0A1729 is ~11.2:1, well
     past the floor the design spec set, and there was no reason to sit on it. */
  --on-dark: #FFFFFF;
  --on-dark-soft: #C7CCD4;

  /* ---- the accent ---- */
  --accent: #04A9AD;       /* sampled from the mark's bolt and "KEYBOARD" */
  /* For hovers. The old pair kept hue and saturation and took HSL lightness
     down to 0.848 of the accent's (#0EB7B6 → #0B9C9B); the same step on
     #04A9AD gives #038F93. */
  --accent-deep: #038F93;

  /* ---- structure ---- */
  --container: 72rem;
  --gutter: clamp(1.25rem, 4vw, 2.5rem);
  --band-y: clamp(3.5rem, 8vw, 6.5rem);
  --radius: 4px;           /* square-shouldered. A hint of a corner, no more. */

  /* ---- one spacing scale. Every margin in this file comes from here. ---- */
  --s1: 0.5rem;
  --s2: 1rem;
  --s3: 1.5rem;
  --s4: 2.5rem;

  /* ---- the type scale, in clamp so it holds from 360 to 2560 ---- */
  --t-hero: clamp(2.4rem, 5vw, 4.25rem);
  --t-h2: clamp(1.6rem, 3vw, 2.25rem);
  --t-h3: clamp(1.125rem, 1.6vw, 1.3rem);
  --t-body: 1.0625rem;
  --t-small: 0.8125rem;
  --measure: 65ch;         /* prose only, never layout */
}

* { box-sizing: border-box; }

/* `hidden` MUST WIN OVER ANY DISPLAY AN AUTHOR RULE SETS.
 *
 * This is not defensive tidiness; it is a live bug this file shipped. The
 * browser's own `[hidden] { display: none }` is an author-beatable UA rule, and
 * the modal's wide-screen block set `display: grid` on `.quiz-modal`. So above
 * 760px the HIDDEN modal was laid out anyway: a full-viewport backdrop at
 * z-index 50, invisible, sitting on top of every page that mounts the quiz.
 * Measured at 1440px on the deployed build — the nav links and the Apply button
 * on `/`, `/work/` and `/roadmap/` were all unclickable. The page was not
 * broken-looking, it was inert.
 *
 * The suite could not have caught it. `test/dom.mjs` has no layout and no
 * hit-testing, and the quiz tests dispatch events straight at elements, so a
 * button covered by an invisible sheet still "clicks". It took a real browser.
 * That is the same lesson as the `hidden`-as-a-plain-property bug in STATE's
 * gotchas, one layer further out: a fixture that cannot be wrong about layout
 * cannot warn you about layout. */
[hidden] { display: none !important; }

html { scroll-behavior: smooth; }

/* THE BODY GROUND IS DARK; `main` carries the white. A short page — the 404 is
   the one that shows it — ended above the fold, and the viewport below the dark
   footer came back white, so the page looked like it had been cut off. Every
   page ends on a dark footer, so a dark body ground reads as that footer
   continuing rather than as a seam. */
body {
  margin: 0;
  background: var(--dark);
  color: var(--ink);
  font: var(--t-body)/1.6 -apple-system, BlinkMacSystemFont, "Helvetica Neue",
        Helvetica, "Segoe UI", Roboto, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}

main { background: var(--white); }

img { max-width: 100%; height: auto; display: block; }
p { margin-block: 0; }

h1, h2, h3 {
  line-height: 1.08;
  letter-spacing: -0.025em;
  margin-block: 0;
  font-weight: 800;
}

/* FOCUS IS A TOKEN, because it has to survive both grounds and the retheme
   proved one colour cannot.
   
   The ring was amber while every surface it landed on was black, and amber on
   black is ~11:1. The retheme took the same rule to teal and turned the bar and
   most bands WHITE — and teal on white measures **2.48:1**, under the 3:1 that
   WCAG 1.4.11 asks of a non-text indicator. On the band it is 2.23:1. Keyboard
   focus went faint on the first thing anybody tabs into, and no assertion could
   see it: it is not text, so the contrast audit skipped it, and no test looked
   at a focus state at all.
   
   So the ring follows the ground the way the greys already do. Ink on white is
   ~16:1; teal on the ink band is 6.8:1. The 3px offset keeps a gap of the
   ground between the ring and whatever it is ringing, so a teal button never
   sits against a teal ring. */
:root { --focus: var(--ink); }
:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; }

/* ------------------------------------------------------- band + container -- */

/* THE BAND. Background spans the viewport; nothing else does. */
section { padding-block: var(--band-y); padding-inline: var(--gutter); }

/* THE CONTAINER. One width, centred, everywhere, and the thing every section
   has to opt out of rather than opt into. */
.wrap {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
}

.band { background: var(--band); border-block: 1px solid var(--line); }

/* TWO BANDS ON THE SAME GROUND ARE ONE BLOCK, so they share one gap instead of
   stacking two. Found by looking, not by reasoning: at 1440 the white proof
   band sat above the white "who we work with" band and their padding added up
   to 208px of nothing between two sentences, and the same thing happened inside
   the black anchor between "How we start" and "How we price" — worse there,
   because a void reads louder on dark. Where the ground actually changes the
   full gap stays, because that is the seam doing its job. */
.band-dark + .band-dark,
.band + .band,
section:not(.band):not(.band-dark):not(.hero) + section:not(.band):not(.band-dark):not(.hero) {
  padding-top: 0;
}

/* The dark anchors — the ownership block at page scale.
 *
 * THE GREYS ARE REMAPPED HERE, not overridden component by component. The first
 * attempt chased each one (`.band-dark .case p`, `.band-dark .crosslink p`) and
 * every one of those quietly failed, because a descendant selector needs
 * `.case` INSIDE `.band-dark` and the markup puts both classes on the same
 * section. Measured in the browser: body copy on the dark case study came out
 * near 1.9:1 — an accessibility failure, not a taste call, and invisible to a
 * stylesheet grep that only reads intent.
 *
 * Redefining the tokens fixes every component at once and cannot miss one.
 * `--ink` is deliberately NOT remapped: it is the ink that sits on teal
 * buttons and number blocks, and it has to stay ink there. */
.band-dark {
  background: var(--dark);
  color: var(--on-dark-soft);
  border-block: 0;
  --ink-soft: var(--on-dark-soft);
  --muted: var(--on-dark-soft);
  --line: var(--dark-line);
  --focus: var(--accent);
}
.hero {
  --ink-soft: var(--on-dark-soft);
  --muted: var(--on-dark-soft);
  --line: var(--dark-line);
  --focus: var(--accent);
}
/* …and the quiz puts them back, because it keeps its light ground wherever it
   is mounted, including inside a dark hero. These literals MUST match the
   root tokens above; they are the light values restated inside a dark scope. */
.quiz-card, .quiz-dialog {
  --ink-soft: #4B515C;
  --muted: #6A717D;
  --line: #DDE1E6;
  --focus: var(--ink);
  color: var(--ink);
}
.band-dark h1, .band-dark h2, .band-dark h3, .band-dark strong { color: var(--on-dark); }
/* `:not(.btn)` is load-bearing. Without it this rule beat `.btn { color: ink }`
   and every accent button on a dark band came out WHITE ON THE ACCENT —
   measured unreadable, and a direct violation of the one palette rule this
   site has. The button's ink-on-accent IS the mark's pairing; a link colour
   must never reach inside it. */
.band-dark a:not(.btn) { color: var(--on-dark); }

/* Prose gets a measure; layout never does. Keeping those apart is what stops a
   three-column grid from being sixty-five characters wide. */
.sub, .stance p, .wedge p, .speed p, .contact p, .price-sub,
.case p, .capacity, .crosslink p, .prose p, .quiz-lede {
  max-width: var(--measure);
}

/* --------------------------------------------------------------- the bar -- */

/* LIGHT, full-bleed, always visible — inverted 2026-09-14 so the bar can hold
   the actual mark. The mark's wordmark is ink; on the old dark bar it could
   only vanish or arrive as a white rectangle (both rendered, both rejected,
   twice, for two different marks). The ground changed instead. It is the first
   pixel a $250K buyer sees, and it is finally the company's own logo. */
.bar {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s2) var(--s4);
  padding: var(--s2) var(--gutter);
  background: var(--white);
  border-bottom: 1px solid var(--line);
}

/* THE MARK. Served trimmed from Cloudinary at 2x, explicit dimensions in the
   markup so the bar never reflows while it loads, rendered height pinned here.
   The baked-in tagline is a texture at this size; it lives full-size on the
   share card, which the founder ratified knowingly. */
.bar-logo { display: inline-flex; align-items: center; padding-block: 2px; }
.bar-logo img { height: 52px; width: auto; display: block; }

.bar-nav {
  display: flex;
  order: 3;                 /* its own line on a phone */
  width: 100%;
  gap: var(--s3);
  padding-top: var(--s1);
  border-top: 1px solid var(--line);
  overflow-x: auto;
}
.bar-nav a {
  font-size: var(--t-small);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
  text-decoration: none;
  white-space: nowrap;
  padding-block: var(--s1);
}
.bar-nav a:hover { color: var(--ink); }

.bar-cta {
  margin-left: auto;
  background: var(--accent);
  color: var(--ink);          /* ink on teal — the mark's pairing, ~7.7:1 */
  font: inherit;
  font-size: var(--t-small);
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border: 0;
  border-radius: var(--radius);
  padding: 0.72em 1.2em;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}
.bar-cta:hover { background: var(--accent-deep); }

@media (min-width: 760px) {
  .bar-nav {
    order: 0;
    width: auto;
    margin-left: auto;
    padding-top: 0;
    border-top: 0;
    overflow-x: visible;
  }
  .bar-nav a { padding-block: 0; }
  .bar-cta { margin-left: 0; }
}

/* -------------------------------------------------------------- the hero -- */

/* The dark anchor at page scale: ink ground, white type, one teal rule above
   the line, teal button. Left-aligned, because a centred hero above a
   left-aligned page is two alignments, which is half of diagnosis #1. */
.hero { background: var(--dark); color: var(--on-dark-soft); }
.hero h1 { color: var(--on-dark); font-size: var(--t-hero); max-width: 20ch; }
/* DIRECT CHILDREN ONLY. The quiz card is mounted inside the hero on
   /websites/, and `.hero .micro` reached straight into it — the reassurance
   line under question one came out unreadable on the card's white, measured.
   The hero's own copy is always a direct child of its container, so `>` says
   what was meant and cannot wander into a white card. */
.hero > .wrap > .sub {
  font-size: clamp(1.0625rem, 1.6vw, 1.25rem);
  color: var(--on-dark-soft);
  margin-top: var(--s3);
}
.hero > .wrap > .btn { margin-top: var(--s4); }
.hero > .wrap > .micro { margin-top: var(--s3); color: var(--on-dark-soft); }
.hero > .wrap > .micro a { color: var(--on-dark); }

/* The accent rule above the positioning line. */
.hero .wrap::before {
  content: "";
  display: block;
  width: 4.5rem;
  height: 3px;
  background: var(--accent);
  margin-bottom: var(--s3);
}

.studio-hero h1 { max-width: 18ch; }

/* ------------------------------------------------------------- headings -- */

h2 { font-size: var(--t-h2); }

/* The section head's accent rule sits ABOVE it, not under it. Under a heading
   it reads as an underline decorating a word; above it, it reads as the top
   edge of a block. */
.wrap > h2:first-child::before {
  content: "";
  display: block;
  width: 3rem;
  height: 3px;
  background: var(--accent);
  margin-bottom: var(--s2);
}
/* ONE RHYTHM INSIDE A BAND, from the same scale as everything else.
   Sibling blocks in a container had NO default spacing at all, which only
   showed up where a section happened to put a paragraph after a list — on
   /roadmap/, "It's yours outright…" sat flush against the last bullet with
   zero gap. Components that manage their own spacing say so below and win by
   coming later; the grid splits switch this off and use `gap`. */
.wrap > * + * { margin-top: var(--s3); }
/* What follows a section head gets one step more, so the head reads as a head
   rather than as the first line of what is under it. */
.wrap > h2 + * { margin-top: var(--s4); }

/* The small uppercase label — the "PRICED BY WRITTEN ESTIMATE" voice. */
.eyebrow, .stat-before, .who-col h3, .quiz-count {
  font-size: var(--t-small);
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.eyebrow { color: var(--ink-soft); margin-bottom: var(--s2); }

.lede {
  font-size: clamp(1.2rem, 2.2vw, 1.5rem);
  font-weight: 800;
  letter-spacing: -0.02em;
  margin-bottom: var(--s2);
}

.micro { font-size: var(--t-small); color: var(--muted); line-height: 1.5; }
.muted { color: var(--muted); font-weight: 400; }
.hint { font-size: var(--t-small); color: var(--muted); }

/* -------------------------------------------------------------- buttons -- */

.btn {
  display: inline-block;
  background: var(--accent);
  color: var(--ink);          /* ink on teal — ~7.7:1 */
  font: inherit;
  font-size: 1rem;
  font-weight: 800;
  letter-spacing: 0.04em;
  border: 0;
  border-radius: var(--radius);
  padding: 1.05em 1.9em;
  text-decoration: none;
  cursor: pointer;
}
.btn:hover { background: var(--accent-deep); }
.btn:active { transform: translateY(1px); }
.btn-block { display: block; width: 100%; margin-top: var(--s1); }

/* ---------------------------------------------------------- the proof -- */

/* Bordered cards on an equal-height grid: 1px ink border, 3px accent top
   rule, small-caps label, the number large, the client pinned to the bottom.
   These used to be borderless list items that escaped the container entirely. */
.stats {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--s3);
}
.stats li {
  display: flex;
  flex-direction: column;
  padding: var(--s3);
  background: var(--white);
  border: 1px solid var(--ink);
  border-top: 3px solid var(--accent);   /* the accent as a rule, never as type */
}
.stat-before { color: var(--ink-soft); margin-bottom: var(--s1); }
.stat-after {
  font-size: clamp(1.5rem, 2.6vw, 2rem);
  font-weight: 800;
  letter-spacing: -0.03em;
  margin-bottom: auto;                    /* the client name pins to the bottom */
  padding-bottom: var(--s2);
}
.stat-who {
  font-size: var(--t-small);
  font-weight: 800;
  color: var(--ink);
  padding-top: var(--s2);
  border-top: 1px solid var(--line);
}
.proof-more { margin-top: var(--s4); }
/* INHERIT, do not name a colour. These links sit on white bands and on dark
   ones, and naming --ink made them dark-on-dark at the foot of / and
   /websites/ — measured invisible rather than merely poor. The first fix was
   `.band-dark .crosslink a`, which cannot match: the markup puts both classes
   on the same section, so there is no descendant to select. Inheriting from
   the band is the version that cannot be wrong. */
.proof-more a, .crosslink a, .index-list a { color: inherit; font-weight: 800; }

/* ------------------------------------------------- who we work with -- */

.who-grid { display: grid; gap: var(--s4); }
.who-col h3 {
  color: var(--ink);
  margin-bottom: var(--s2);
  padding-bottom: var(--s1);
  border-bottom: 3px solid var(--accent);
}
/* The refusals are not a warning label, so they are not coloured like one.
   Same weight as the column beside them; the content does the work. */
.who-col + .who-col h3 { border-bottom-color: var(--line); }
/* The rule above the capacity sentence spans the container, not the sentence.
   Measured at 65ch it stopped at 60% of the width and read as an arbitrary
   dash rather than as the top edge of a closing statement. */
.capacity {
  margin-top: var(--s4);
  padding-top: var(--s3);
  border-top: 1px solid var(--line);
  font-size: clamp(1.0625rem, 1.6vw, 1.2rem);
  font-weight: 700;
  max-width: none;
}
.capacity span, .capacity { display: block; }
.band-dark .capacity { border-top-color: var(--dark-line); }

/* ------------------------------------------------------- lists + prose -- */

.bullets {
  color: inherit;
  margin: 0;
  padding-left: 1.15rem;
  display: grid;
  gap: var(--s2);
  max-width: var(--measure);
}
.reasons {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--s2);
  max-width: var(--measure);
}
.reasons li { position: relative; padding-left: 1.6rem; }
.reasons li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.62em;
  width: 0.75rem;
  height: 3px;
  background: var(--accent);   /* the accent as a rule, standing in for a bullet */
}

.prose h2 { margin-top: var(--s4); }
.prose p { margin-block: 0 var(--s2); }
/* A link inside prose is ink with a rule under it, never the browser's blue —
   found by looking at the privacy page, which is the first page whose copy
   carries links in the middle of sentences (2026-09-16). The rule is the
   affordance; the colour stays the text's, so a paragraph reads as a paragraph.
   Teal is never a text colour here, the same law the app follows. */
.prose a { color: var(--ink); text-decoration-color: var(--line); text-underline-offset: 3px; }
.prose a:hover { text-decoration-color: var(--ink); }
.index-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s2); }
.index-list a { color: var(--ink); font-weight: 800; }

/* ------------------------------------------------------------- cards -- */

.cards { display: grid; gap: var(--s3); }
.card {
  padding: var(--s3);
  background: var(--white);
  border: 1px solid var(--line);
  border-top: 3px solid var(--ink);
}
.card h3 { font-size: var(--t-h3); margin-bottom: var(--s1); }
.card p { color: var(--ink-soft); }

/* ------------------------------------------------------------- steps -- */

.steps { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s4); }
.steps li { display: grid; grid-template-columns: auto 1fr; gap: var(--s1) var(--s3); }
.step-n {
  grid-row: span 2;
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  background: var(--accent);
  color: var(--ink);            /* ink on teal, not teal type */
  font-size: 1.1rem;
  font-weight: 800;
}
.steps h3 { font-size: var(--t-h3); align-self: center; }
.steps p { color: var(--ink-soft); max-width: var(--measure); }
/* The greys come from the remapped tokens above; only the things that must be
   FULL white rather than body grey are named here. */
.band-dark .case-stat, 
/* --------------------------------------------------- wedge / stance -- */

.wedge .btn, .stance .btn { margin-top: var(--s3); }
.stance .micro { margin-top: var(--s3); }

/* --------------------------------------------------------------- price -- */

/* One colour. Marking up part of the figure — the number in white, the rest in
   the band's body grey — read as a deliberate two-tone nobody chose. The figure
   is one sentence and it is said in one voice, whatever the number is. */
.band-dark .price-figure { color: var(--on-dark); }
.price-figure {
  font-size: clamp(2.1rem, 4.5vw, 3.25rem);
  font-weight: 800;
  letter-spacing: -0.035em;
  margin-bottom: var(--s2);
}
.price-sub { margin-bottom: var(--s3); }
.price .micro { margin-top: var(--s3); }

/* ------------------------------------------------------------- contact -- */

.contact .btn { margin-top: var(--s3); }
.contact .micro { margin-top: var(--s2); }

/* ----------------------------------------------------------- crosslink -- */

/* Each page names the audience it is NOT for and points at the page that is.
   Quiet on purpose: a visitor on the right page should read past it. */
.crosslink { padding-block: var(--s4); }
.crosslink p { font-size: 0.95rem; }

/* --------------------------------------------------------------- /work/ -- */

.case h2 { margin-bottom: var(--s2); }
.case h3 { font-size: var(--t-h3); margin-top: var(--s4); margin-bottom: var(--s2); }
.case p { color: var(--ink-soft); }
.case-stat {
  font-size: clamp(1.2rem, 2.2vw, 1.5rem);
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--ink);
  padding-left: var(--s3);
  border-left: 4px solid var(--accent);   /* the accent as a bar, not as the words */
  margin-bottom: var(--s2);
}
.case .bullets { margin-bottom: var(--s3); }

/* ---------------------------------------------------------- page heros -- */

.page-hero h1 { font-size: clamp(2rem, 4vw, 3.25rem); }
.page-hero .sub { margin-top: var(--s3); }
.page-hero .btn { margin-top: var(--s4); }
.page-hero .micro { margin-top: var(--s3); }
.page-article h1 { font-size: clamp(2rem, 4vw, 3rem); margin-bottom: var(--s3); }

/* ------------------------------------------------------ built-by pages -- */

/* A client site's credit lands here (partials/built.njk). The page is the
   site's own parts — hero, band, cards, dark close — plus the one thing no
   other page has: a large screenshot of somebody else's live site. Everything
   below is tokens. The accent stays a rule and a ground; nothing here sets it
   as type. */
.built-hero h1 { max-width: 22ch; }
/* The two doors are one row that wraps. `.page-hero .btn` gives EVERY button a
   top margin, which is right for a single CTA and wrong here: at 360 the second
   button wrapped and carried its own 2.5rem, so the pair read as two unrelated
   blocks. The row takes the margin once; the flex gap spaces the buttons. */
.built-hero .actions { margin-top: var(--s4); }
.built-hero .actions .btn { margin-top: 0; }

/* THE SCREENSHOT is content inside the container like everything else — the
   one-container rule does not bend for a picture. A hairline frames it,
   because the client's site may itself be white and would otherwise dissolve
   into our white band. */
.built-shot-link { display: block; border: 1px solid var(--line); }
.built-shot-link:hover { border-color: var(--ink); }
.built-shot img, .built-list img { display: block; width: 100%; height: auto; }
.built-shot .micro a, .built-list .micro a { color: var(--ink); }

/* Four cards read as two pairs; three across would orphan the fourth. */
@media (min-width: 900px) {
  .cards.built-cards { grid-template-columns: repeat(2, 1fr); }
}
.built-also { max-width: var(--measure); color: var(--ink-soft); }
.built-also strong { color: var(--ink); }

.built-quote { margin: 0; padding-left: var(--s3); border-left: 4px solid var(--accent); max-width: var(--measure); }
.built-quote p { font-size: var(--t-h3); font-weight: 700; color: var(--ink); }
.built-quote footer { margin-top: var(--s1); font-size: var(--t-small); color: var(--muted); }

.built-close .actions { margin-top: var(--s3); }

/* The /built/ index: every page as its screenshot and its name. */
.built-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s4); }
@media (min-width: 900px) {
  .built-list { grid-template-columns: repeat(2, 1fr); }
}
.built-list-link { display: grid; gap: var(--s2); color: var(--ink); text-decoration: none; }
.built-list-link img { border: 1px solid var(--line); }
.built-list-link:hover img { border-color: var(--ink); }
.built-list-name { font-size: var(--t-h3); font-weight: 800; }
.built-list .micro { margin-top: var(--s1); }

/* ------------------------------------------------------------- footer -- */

/* The footer is a dark ground and has to remap like one. It was not in the
   remap group, so it kept the light focus ring: ink on the ink footer measured
   **1.09:1** — an invisible outline on the last link of every page. Same class
   of miss as the retheme's teal-on-white ring, one element further down. Any
   surface that sets `--dark` as its background remaps with it. */
.foot {
  padding: var(--s4) var(--gutter);
  background: var(--dark);
  --focus: var(--accent);
  --line: var(--dark-line);
  color: var(--on-dark-soft);
  font-size: var(--t-small);
}
.foot strong { color: var(--on-dark); }
.foot a { color: var(--on-dark-soft); }
.foot a:hover { color: var(--on-dark); }

/* ------------------------------------------------------------- the 404 -- */

/* The one page nobody is meant to reach, dressed like the rest of the site so
   arriving here reads as a wrong turn rather than as something broken. */
.hero-404 .eyebrow { color: var(--on-dark-soft); }
.hero-404 h1 { max-width: 20ch; }
.hero-404 .btn { margin-top: var(--s4); }

/* ---------------------------------------------------------------- wide -- */

@media (min-width: 760px) {
  .who-grid { grid-template-columns: 1fr 1fr; }
  .reasons { grid-template-columns: 1fr 1fr; gap: var(--s2) var(--s4); }
  .row { grid-template-columns: 1fr 1fr; }
  .steps { grid-template-columns: repeat(3, 1fr); }
  .steps li { grid-template-columns: 1fr; align-content: start; }
  .step-n { grid-row: auto; }
}

/* Three across only once there is room for three. At 768 the proof cards were
   ~220px each and every label wrapped to three lines — technically a grid,
   visually a squeeze. Looked at, then moved. */
@media (min-width: 900px) {
  .cards { grid-template-columns: repeat(3, 1fr); }
  .stats { grid-template-columns: repeat(3, 1fr); }
}

/* ------------------------------------------------ the editorial split -- */

/* A heading alone above a 65-character paragraph leaves the right half of the
   container empty, and on the black anchor that empty half is 600px of nothing
   at 1440 — the "dead zone" half of the diagnosis, arriving by a different
   route than the islands did. Above 1100px the prose sections put the heading
   in a left column and everything else in a right one, which is what an
   editorial page does with the same material.
   
   No markup changes: the h2 pins to row one of column one, and every other
   child auto-places down column two. */
@media (min-width: 1100px) {
  .wedge .wrap, .stance .wrap, .speed .wrap, .contact .wrap, .price .wrap {
    display: grid;
    grid-template-columns: minmax(12rem, 18rem) 1fr;
    gap: var(--s3) clamp(2rem, 5vw, 4.5rem);
    align-items: start;
    grid-auto-rows: min-content;
  }
  .wedge .wrap > * + *, .stance .wrap > * + *, .speed .wrap > * + *,
  .contact .wrap > * + *, .price .wrap > * + * { margin-top: 0; }
  .wedge .wrap > h2, .stance .wrap > h2, .speed .wrap > h2,
  .contact .wrap > h2, .price .wrap > h2 {
    grid-column: 1;
    grid-row: 1;
    margin-bottom: 0;
  }
  .wedge .wrap > :not(h2), .stance .wrap > :not(h2), .speed .wrap > :not(h2),
  .contact .wrap > :not(h2), .price .wrap > :not(h2) { grid-column: 2; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .btn:active { transform: none; }
}

/* ================= the quiz =================
   Four screens or ten, one question each. A long form asks somebody to decide
   up front whether the whole thing is worth their time; one question at a time
   asks for one small answer, and each is easier than the last.

   THE QUIZ STAYS ON A LIGHT GROUND on both mounts. The bands around it are
   dark; a form did not follow them there. Filling in eight fields of white
   text on dark is harder than it needs to be, and the one place on this site
   where legibility outranks identity is the place somebody is typing. */

.quiz { max-width: 100%; color: var(--ink); }

.quiz-progress {
  display: flex;
  align-items: center;
  gap: var(--s2);
  margin-bottom: var(--s3);
}
.quiz-bar {
  flex: 1;
  height: 3px;
  background: var(--line);
  overflow: hidden;
}
.quiz-bar-fill {
  display: block;
  height: 100%;
  width: 25%;
  background: var(--accent);   /* the accent as a block */
  transition: width 180ms ease;
}
.quiz-count { color: var(--ink-soft); white-space: nowrap; }

.quiz-step { border: 0; padding: 0; margin: 0; display: grid; gap: var(--s2); }
.quiz-q {
  padding: 0;
  font-size: clamp(1.2rem, 2.2vw, 1.5rem);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.15;
}
.quiz-lede { color: var(--ink-soft); }

.quiz-options { display: grid; gap: var(--s1); }
.quiz-option {
  position: relative;
  width: 100%;
  text-align: left;
  background: var(--white);
  color: var(--ink);
  font: inherit;
  font-weight: 700;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 14px 16px;
  cursor: pointer;
}
.quiz-option:hover { border-color: var(--ink); }
.quiz-option:active { background: var(--band); }
/* Selected: a 2px ink border and an accent rule down the leading edge. The
   accent marks the choice; it never becomes the words. */
.quiz-option[aria-pressed="true"] {
  border: 2px solid var(--ink);
  padding: 13px 15px;
  box-shadow: inset 4px 0 0 0 var(--accent);
}

.quiz-step label > span {
  display: block;
  font-size: var(--t-small);
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: 6px;
}
.quiz-step input[type="text"],
.quiz-step input[type="tel"],
.quiz-step input[type="email"],
.quiz-text {
  width: 100%;
  font: inherit;
  color: var(--ink);
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 13px 14px;
}
.quiz-text { resize: vertical; min-height: 96px; }
.quiz-step input:focus, .quiz-text:focus { border-color: var(--ink); }

.quiz-options + input[type="text"] { margin-top: var(--s1); }

.row { display: grid; gap: var(--s2); }

/* The honeypot: named like something a bot would want to fill, hidden by CSS
   rather than by type=hidden, which better bots skip. */
.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form-note {
  border: 1px solid var(--line);
  border-left: 4px solid var(--ink);
  background: var(--band);
  padding: 12px 14px;
  font-size: 0.95rem;
}
.form-note.is-error { border-left-color: #8C3A2C; background: #FCF3F1; color: #8C3A2C; }
.form-note.is-done { border-left-color: var(--accent); background: #E9F7F7; }

.quiz-back {
  justify-self: start;
  background: none;
  border: 0;
  padding: var(--s1) 0;
  font: inherit;
  font-size: var(--t-small);
  font-weight: 700;
  color: var(--muted);
  cursor: pointer;
}
.quiz-back:hover { color: var(--ink); }

.quiz-done { padding: var(--s2) 0; }
.quiz-tick {
  width: 2.75rem;
  height: 2.75rem;
  display: grid;
  place-items: center;
  background: var(--accent);
  color: var(--ink);
  font-size: 1.4rem;
  font-weight: 800;
  margin-bottom: var(--s2);
}
.quiz-done h3 { font-size: clamp(1.1rem, 2vw, 1.3rem); margin-bottom: var(--s2); }

/* The exit. Reached only from the budget gate's No, and it sends nothing.
   Deliberately not styled as a failure: no red, no apology tone, and the three
   criteria get the same weight as any other list on the site. */
.quiz-exit { padding: var(--s1) 0; }
.quiz-exit h3 { font-size: clamp(1.2rem, 2.2vw, 1.45rem); margin-bottom: var(--s2); }
.quiz-exit p { color: var(--ink-soft); margin-bottom: var(--s3); }
.quiz-exit .bullets { margin-bottom: var(--s3); }
.quiz-exit .bullets strong { color: var(--ink); }

/* ---- the inline mount ---- */

/* On /websites/ the quiz sits in the hero, which is a dark band. The card is a
   white block on that dark — the anchor-band move — so the form keeps its
   light ground without the hero losing its. */
.quiz-card {
  margin-top: var(--s4);
  padding: var(--s4) var(--s3);
  background: var(--white);
  border-top: 3px solid var(--accent);
  text-align: left;
  max-width: 40rem;
}
.quiz-micro { margin-top: var(--s2); }

/* ---- the modal mount ---- */

.quiz-modal { position: fixed; inset: 0; z-index: 50; }
.quiz-backdrop { position: absolute; inset: 0; background: rgba(10, 23, 41, 0.72); }  /* the navy, as a scrim */
.quiz-dialog {
  position: relative;
  max-width: 40rem;
  margin: 0 auto;
  /* Full height on a phone, a card above it. The application reaches ten
     screens, so the dialog scrolls rather than clipping. */
  max-height: 100%;
  height: 100%;
  overflow-y: auto;
  background: var(--white);
  border-radius: 0;
  border-top: 3px solid var(--accent);
  padding: 54px 20px 28px;
}
.quiz-close {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  background: none;
  border: 0;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--muted);
  cursor: pointer;
}
.quiz-close:hover { color: var(--ink); }
.quiz-title { font-size: clamp(1.35rem, 2.6vw, 1.75rem); margin-bottom: var(--s2); }
.quiz-sub { color: var(--ink-soft); margin-bottom: var(--s3); }

@media (min-width: 760px) {
  .quiz-modal { display: grid; place-items: center; padding: 24px; }
  .quiz-dialog {
    height: auto;
    max-height: calc(100vh - 48px);
    border-radius: var(--radius);
    padding: 54px 34px 30px;
  }
  .quiz-card { padding: var(--s4); }
}

/* THE PRODUCT HERO SPLITS at wide widths: the offer on the left, the form on
   the right. The quiz card is capped at 40rem, so in a 72rem container it left
   the right half of a dark hero empty — the same dead zone the prose sections
   had, arriving through the form instead. Side by side, the headline and the
   thing it asks you to do are finally in the same eyeline. */
@media (min-width: 1100px) {
  .hero:has(.quiz-card) .wrap {
    display: grid;
    grid-template-columns: 1fr minmax(22rem, 30rem);
    gap: var(--s3) clamp(2rem, 5vw, 4rem);
    align-items: start;
    /* Without these the card's four-row span pushes its height back into the
       left column's rows, and the subline drifts half a hero away from the
       headline it belongs to. Rows size to their own content; the leftover goes
       under the shorter column. */
    grid-auto-rows: min-content;
    align-content: start;
  }
  .hero:has(.quiz-card) .wrap > * + * { margin-top: 0; }
  .hero:has(.quiz-card) .wrap > :not(.quiz-card) { grid-column: 1; }
  .hero .quiz-card {
    grid-column: 2;
    grid-row: 1 / span 4;
    margin-top: 0;
    max-width: none;
  }
}

/* ================== the intro-call picker ==================
   Two doors on the studio pages: a twenty-minute call and the application. The
   call leads because it costs a stranger less, and the application stays beside
   it because somebody who already knows what they want should not have to sit
   through a call to say it. */

.actions { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center; }
.actions + .capacity-line { margin-top: var(--s3); }

/* The second door. A button, not a link — it does the same kind of thing as the
   one beside it, and dressing it as a link would suggest it does not. */
.btn-ghost {
  background: transparent;
  color: var(--ink);
  border: 2px solid var(--ink);
  padding: calc(1.05em - 2px) calc(1.9em - 2px);
}
.btn-ghost:hover { background: var(--ink); color: var(--white); }
.band-dark .btn-ghost, .hero .btn-ghost { color: var(--on-dark); border-color: var(--on-dark); }
.band-dark .btn-ghost:hover, .hero .btn-ghost:hover { background: var(--on-dark); color: var(--ink); }

/* The rule's own number, quoted on the page. If the knobs change this line
   changes with them, and a test holds the two together. */
.capacity-line { font-weight: 700; color: var(--ink-soft); }

/* The bar's second door. Quieter than the CTA beside it and still a target. */
.bar-alt {
  background: none;
  border: 0;
  font: inherit;
  font-size: var(--t-small);
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
  padding: 0.72em 0;
  cursor: pointer;
  white-space: nowrap;
}
.bar-alt:hover { color: var(--ink); }
.bar-cta + .bar-alt { margin-left: var(--s3); }

/* ---- the slot chips ---- */

/* Grouped under the day they fall on, because "Wed, Sep 23 · 3:00 PM" twice in
   a column reads as a list of strings until something says which day is which. */
.slot-day {
  margin-top: var(--s2);
  font-size: var(--t-small);
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.slot-day:first-child { margin-top: 0; }

/* The booked-out and could-not-load states. Both hand over the other door
   rather than ending the conversation, which is why they look like a block of
   content and not like an error. */
.quiz-aside {
  padding: var(--s3);
  background: var(--band);
  border-left: 4px solid var(--accent);
}
.quiz-aside p { margin-bottom: var(--s2); }

.call-when { font-weight: 700; margin-bottom: var(--s2); }

@media (min-width: 760px) {
  /* Two chips to a row once there is space, which is the shape of the rule:
     each day offers a morning and an afternoon. */
  [data-slot-options] { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s1); }
  .slot-day { grid-column: 1 / -1; }
}
