/* Adapton - core.css
   Loads on EVERY page. Tokens, the board, type, buttons, nav, footer, intro curtain, circuit
   dividers, sections, FAQ, the hero shell and the numbered track.

   :root, [data-theme="light"] and @font-face live HERE AND NOWHERE ELSE. If you are writing a
   hex outside those two blocks, the token you need is missing - add it, do not hardcode.
   A component used by more than one page type belongs here. When in doubt it belongs here:
   a duplicated rule costs bytes, a missing one breaks a page. */

/* ═══════════════════════════════════════════════════════════════
   Adapton — adapton.io

   Tokens are ROLES, not colours, so one set of rules serves both
   themes. Brand constants (navy, orange) never move; everything
   else flips on [data-theme].

   The argument the palette makes, in both themes:
     orange = what Adapton catches, and every action
     grey   = what is lost — missed, unanswered, not included
     navy   = structure, the caller, the board itself
   ═══════════════════════════════════════════════════════════════ */

@font-face {
  font-family: "Space Grotesk";
  src: url("/assets/fonts/SpaceGrotesk-var.woff2") format("woff2-variations"),
       url("/assets/fonts/SpaceGrotesk-var.ttf") format("truetype-variations");
  font-weight: 300 700;
  font-display: swap;
}

:root {
  /* ── brand constants — identical in both themes ── */
  --navy:      #185088;
  --navy-deep: #103860;
  --orange:    #F89840;
  --on-orange: #16100A;      /* text sitting ON orange */

  /* ── dark theme (default) ── */
  --bg:        #0C0F17;
  --bg-panel:  #10141E;
  --bg-card:   #151B28;
  --bg-raised: #1B2334;

  --text:      #F6F8FB;      /* primary copy */
  --text-head: #C8CDD4;      /* headings — brushed steel */
  --text-dim:  #8A93A3;      /* secondary, and "lost" */
  --text-faint:#808A99;      /* 4.95:1 on --bg — the floor, do not darken */

  --accent:    #F89840;      /* orange as TEXT — safe on dark */
  --accent-soft: rgba(248, 152, 64, .13);
  --accent-line: rgba(248, 152, 64, .32);

  --line:        rgba(200, 205, 212, .10);
  --line-strong: rgba(200, 205, 212, .18);
  --trace:       rgba(24, 80, 136, .48);

  --board:     url("/assets/circuit-pattern.jpg");
  --board-op:  .13;          /* global background strength */
  --hero-op:   .55;
  --card-glow: rgba(24, 80, 136, .16);
  --shadow:    0 24px 60px -24px rgba(0, 0, 0, .7);

  --font: "Space Grotesk", -apple-system, "Segoe UI", sans-serif;
  --mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;

  --gap:    clamp(4.2rem, 8vw, 7.5rem);
  --radius: 14px;
  --swipe:  cubic-bezier(.76, 0, .24, 1);
  --spring: cubic-bezier(.22, .9, .3, 1);
}

[data-theme="light"] {
  --bg:        #F4F6FA;
  --bg-panel:  #ECF0F7;
  --bg-card:   #FFFFFF;
  --bg-raised: #FFFFFF;

  --text:      #101828;
  --text-head: #0B1220;
  --text-dim:  #56637A;
  --text-faint:#626D80;      /* 4.78:1 on --bg — the floor, do not lighten */

  /* Full orange is 2.1:1 on this ground — unreadable. Same hue, darkened
     to 4.67:1. The orange BUTTON keeps the brand value; only TEXT moves. */
  --accent:    #A85708;
  --accent-soft: rgba(248, 152, 64, .16);
  --accent-line: rgba(180, 94, 6, .34);

  --line:        rgba(16, 32, 52, .13);
  --line-strong: rgba(16, 32, 52, .22);
  --trace:       rgba(24, 80, 136, .34);

  --board:     url("/assets/circuit-pattern-light.jpg");
  --board-op:  .30;
  --hero-op:   .68;
  --card-glow: rgba(24, 80, 136, .07);
  --shadow:    0 20px 46px -22px rgba(16, 32, 52, .28);
}

*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
/* Hero bleed and the ribbon overhang on purpose. clip, not hidden —
   hidden would break the sticky nav. */
html, body { overflow-x: clip; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: clamp(1rem, .97rem + .16vw, 1.0625rem);
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  transition: background .35s ease, color .35s ease;
}

/* ── The board. The picture, as the page's ground. ────────────
   Fixed so the whole site sits on one continuous circuit. */
body::before {
  content: ""; position: fixed; inset: 0; z-index: -1;
  background: var(--board) center / cover no-repeat;
  opacity: var(--board-op);
  pointer-events: none;
}

h1, h2, h3 { margin: 0; font-family: var(--font); font-weight: 600; }
p { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
a { color: inherit; }
img, video { max-width: 100%; display: block; }
b { color: var(--text); font-weight: 600; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 3px; }

.skip {
  position: absolute; left: -9999px; z-index: 99;
  background: var(--orange); color: var(--on-orange);
  padding: .8rem 1.2rem; text-decoration: none; font-weight: 500;
}
.skip:focus { left: 1rem; top: 1rem; }

.wrap { width: min(1260px, 100% - 3rem); margin-inline: auto; position: relative; z-index: 1; }
.wrap--narrow { width: min(820px, 100% - 3rem); }
.center { text-align: center; }

/* ═══ INTRO CURTAIN ═══════════════════════════════════════════
   Decided in an inline <head> script so it never flashes. */

.intro { display: none; }
html.intro-play { overflow: hidden; }
html.intro-play .intro { display: block; position: fixed; inset: 0; z-index: 999; }

.intro__curtain {
  position: absolute; inset: 0;
  background:
    radial-gradient(120% 90% at 50% 10%, var(--card-glow), transparent 60%),
    var(--bg);
  display: flex; align-items: center; justify-content: center;
}
.intro__curtain::before {
  content: ""; position: absolute; inset: 0;
  background: var(--board) center / cover no-repeat;
  opacity: calc(var(--board-op) * 2.2);
  -webkit-mask-image: radial-gradient(80% 70% at 50% 45%, #000 30%, transparent 75%);
          mask-image: radial-gradient(80% 70% at 50% 45%, #000 30%, transparent 75%);
}
.intro__edge {
  position: absolute; left: 0; right: 0; bottom: -2px; height: 2px;
  background: linear-gradient(90deg, transparent, var(--orange) 18%, var(--orange) 82%, transparent);
  box-shadow: 0 0 18px 2px rgba(248, 152, 64, .55);
  opacity: 0;
}

.intro__stage { position: relative; text-align: center; padding: 0 1.5rem; }

.intro__mark { width: clamp(58px, 9vw, 84px); margin: 0 auto 1.6rem; position: relative; }
.intro__mark img { width: 100%; height: auto; }
html.intro-play .intro__mark { animation: intro-mark .62s var(--spring) both; }
@keyframes intro-mark {
  from { opacity: 0; transform: scale(.6) translateY(12px); }
  to   { opacity: 1; transform: scale(1) translateY(0); }
}

.intro__ring { position: absolute; inset: -30%; width: 160%; height: 160%; }
.intro__ring circle {
  fill: none; stroke: var(--line-strong); stroke-width: 1;
  stroke-dasharray: 100; stroke-dashoffset: 100;
}
html.intro-play .intro__ring circle { animation: intro-ring .9s .15s ease-out forwards; }
@keyframes intro-ring { to { stroke-dashoffset: 0; }
 }

.intro__word {
  display: flex; justify-content: center; overflow: hidden;
  font-size: clamp(2.6rem, 9vw, 4.8rem);
  font-weight: 640; letter-spacing: .04em; line-height: 1.06;
  color: var(--text-head);
}
.intro__word span { display: inline-block; transform: translateY(120%); }
html.intro-play .intro__word span {
  animation: intro-rise .58s var(--spring) forwards;
  animation-delay: calc(.28s + var(--i) * .048s);
}
@keyframes intro-rise { to { transform: translateY(0); }
 }

.intro__tag {
  margin-top: .9rem;
  font-family: var(--mono);
  font-size: clamp(.62rem, 1.6vw, .74rem);
  letter-spacing: .44em; text-indent: .44em;
  text-transform: uppercase; color: var(--text-dim);
  opacity: 0;
}
html.intro-play .intro__tag { animation: intro-fade .5s .78s ease-out forwards; }
@keyframes intro-fade { to { opacity: 1; }
 }

.intro__rule {
  display: block; height: 2px; width: min(210px, 40vw);
  margin: 1.1rem auto 0;
  background: linear-gradient(90deg, var(--navy), var(--orange));
  transform: scaleX(0); transform-origin: left center;
}
html.intro-play .intro__rule { animation: intro-rule .55s .9s var(--spring) forwards; }
@keyframes intro-rule { to { transform: scaleX(1); }
 }

html.intro-done .intro__curtain { transform: translateY(-103%); transition: transform .72s var(--swipe); }
html.intro-done .intro__stage {
  transform: translateY(70px); opacity: .4;
  transition: transform .72s var(--swipe), opacity .7s ease;
}
html.intro-done .intro__edge { opacity: 1; }
html.intro-done .intro { pointer-events: none; }

.intro__skip {
  position: absolute; bottom: 1.4rem; left: 50%; transform: translateX(-50%);
  font-family: var(--mono); font-size: .74rem; letter-spacing: .18em;
  text-transform: uppercase; color: var(--text-faint);
  opacity: 0;
}
html.intro-play .intro__skip { animation: intro-fade .5s 1.15s ease-out forwards; }

/* ═══ NAV ═════════════════════════════════════════════════════ */

.nav {
  position: sticky; top: 0; z-index: 40;
  width: 100%;
  background: color-mix(in srgb, var(--bg) 84%, transparent);
  backdrop-filter: blur(14px) saturate(1.3);
  border-bottom: 1px solid transparent;
  transition: border-color .2s ease, background .35s ease;
}
.nav.is-stuck { border-bottom-color: var(--line); }

.nav__in {
  display: flex; align-items: center; gap: 1.4rem;
  width: min(1260px, 100% - 3rem);
  margin-inline: auto;
  padding: .85rem 0;
}

/* The lockup, shipped as the artwork rather than rebuilt in CSS. The wordmark and the
   tracked-out tagline are locked to each other in the file, so nothing can drift when the
   font or the nav height changes.

   It is navy-on-transparent, which is correct on the light theme and unreadable on the dark
   one, so the dark theme swaps in a second file. See the note below before touching either. */
.brand { display: inline-flex; align-items: center; text-decoration: none; margin-right: auto; }
.brand__lockup { display: block; width: auto; height: 34px; }
.brand--foot .brand__lockup { height: 30px; }

/* TWO ARTWORKS, NOT A FILTER. The lockup is navy type plus an orange mark; the navy needs
   lifting on the ink background and the orange must not move. `filter: brightness(1.45)` did
   both at once and turned #F89840 into #FFDC5C -- yellow -- because red was already at 248 and
   clipped. tools/make-dark-lockup.mjs bakes the dark file instead, leaving the orange untouched
   pixel for pixel. Never reintroduce a brightness filter here.

   THE DARK FILE IS STILL BLUE. It lifts navy #185088 to #2E7FD1 on the same hue (210 deg) --
   it is not a steel or white reverse. A steel version shipped 2026-08-19 and was rejected the
   same day: it cleared contrast but read as a different logo. Regenerate, never hand-edit. */
.brand__lockup--dark { display: none; }
[data-theme="dark"] .brand__lockup--light { display: none; }
[data-theme="dark"] .brand__lockup--dark { display: block; }

@media (max-width: 560px) { .brand__lockup { height: 28px; } }

.nav__links { display: flex; gap: 1.5rem; }
.nav__links a {
  font-size: .9rem; font-weight: 500; color: var(--text-dim);
  text-decoration: none; transition: color .16s ease;
}
.nav__links a:hover { color: var(--text); }

/* theme toggle — one button, two icons, swapped by the theme */
.themer {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; flex: none;
  background: transparent;
  border: 1px solid var(--line-strong);
  border-radius: 9px;
  color: var(--text-dim);
  cursor: pointer;
  transition: color .18s ease, border-color .18s ease, background .18s ease;
}
.themer:hover { color: var(--accent); border-color: var(--accent-line); }
.themer svg { width: 17px; height: 17px; }
.themer .i-sun  { display: none; }
[data-theme="light"] .themer .i-sun  { display: block; }
[data-theme="light"] .themer .i-moon { display: none; }

/* ═══ BUTTONS ═════════════════════════════════════════════════ */

.btn {
  position: relative;
  display: inline-flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .1rem;
  padding: .95rem 1.7rem;
  background: var(--orange);
  color: var(--on-orange);
  border: 0; border-radius: 11px;
  font-family: var(--font);
  font-size: .95rem; font-weight: 560;
  text-decoration: none; text-align: center;
  overflow: hidden;
  transition: transform .18s var(--spring), box-shadow .2s ease, background .18s ease;
}
.btn:hover {
  background: #FFB468;
  transform: translateY(-1px);
  box-shadow: 0 10px 30px -10px rgba(248, 152, 64, .55);
}

/* Trace sweep — pointer devices only. On touch there is no hover to
   trigger it, and a full-bleed button shows its resting edge. */
@media (hover: hover) {
  .btn::after {
    content: ""; position: absolute; inset: -1px;
    background: linear-gradient(105deg, transparent 38%, rgba(255,255,255,.34) 50%, transparent 62%);
    transform: translateX(-140%);
    transition: transform .5s ease;
    pointer-events: none;
  }
  .btn:hover::after { transform: translateX(140%); }
  .btn--ghost::after { display: none; }
}

.btn--ghost {
  background: transparent; color: var(--text-head);
  border: 1px solid var(--line-strong);
}
.btn--ghost:hover {
  background: color-mix(in srgb, var(--text) 5%, transparent);
  color: var(--text); border-color: var(--text-dim);
  box-shadow: none;
}

.btn--sm { padding: .55rem 1.05rem; font-size: .875rem; border-radius: 9px; }
.btn--lg { padding: 1.15rem 2.3rem; font-size: 1.05rem; }
.btn__label { font-weight: 560; }
.btn__sub { font-family: var(--mono); font-size: .78rem; letter-spacing: .02em; opacity: .75; }

/* ═══ SHARED TYPE ═════════════════════════════════════════════ */

/* The section marker. It was .68rem at weight 500 and read as fine print -- at a glance the
   sections looked unlabelled. Bigger, heavier, and with a rule in front of it so it carries
   weight as an object rather than as text. The hero's own eyebrow keeps the plain form; it
   already sits under the availability pill and a second mark there is clutter. */
/* The section NAME carries the display size. Tracking comes right down as the size goes up —
   the .16em that opened it at 15px would tear it apart at 46px. */
.eyebrow {
  display: block;
  font-family: var(--mono);
  font-size: clamp(1.7rem, 1.15rem + 2.2vw, 2.85rem);
  font-weight: 500;
  letter-spacing: .02em; text-transform: uppercase;
  line-height: 1.1;
  color: var(--accent);
  /* Zero. Every block that follows a section name carries its own top margin; keeping one here
     too was stacking the two into a ~4.8rem hole under every heading. */
  margin-bottom: 0;
}
@media (max-width: 560px) { .eyebrow { letter-spacing: 0; } }
/* ── The section scale ────────────────────────────────────────
   Three steps, the same in every section on every page, so a reader learns the site once:

     .eyebrow        display, mono, orange   the section NAME. One or two words.
     .section__tag   one line, medium        the claim
     .section__lede / body                   everything else

   THE NAME IS THE HEADING. It is set as <h2 class="eyebrow">, and it is the largest thing in
   the section. This was the other way round until 2026-08-19 -- a 0.68rem kicker over a large
   .h2 -- and the offer pages were still written for the old order for a day afterwards, which
   is what made a category out-shout the headline under it. If you are adding a section and
   reach for a big <h2>, you want .eyebrow.

   The gap between heading and body is made by ENLARGING THE HEADING, never by shrinking the
   body. Body copy below 16px is where a page starts costing readers on a phone. */

/* Kept, unused as of 2026-08-19. A short firm label for a heading that sits UNDER a section
   name -- never a section heading on its own, which is exactly what it was being used as. */
.h2 {
  font-size: 1rem;
  font-weight: 640; line-height: 1.35; letter-spacing: .01em;
  color: var(--text-head);
}

.section__tag {
  max-width: 40ch;
  margin-top: .55rem;
  color: var(--text);
  font-size: clamp(1.1rem, 1rem + .45vw, 1.32rem);
  font-weight: 500; line-height: 1.4;
}
.center .section__tag { margin-inline: auto; }

.section__lede {
  max-width: 56ch;
  margin-top: 1.4rem;
  color: var(--text-dim);
  font-size: 1rem; line-height: 1.65;
}
.center .section__lede { margin-inline: auto; }

.note { margin-top: 1.3rem; font-size: .85rem; color: var(--text-faint); max-width: 46ch; }
.note a { color: var(--text-dim); }
.note--center { margin-inline: auto; text-align: center; }

/* ── Section-foot CTA ────────────────────────────────────────────────────────
   One email route, repeated where a section ends on a decision the reader has
   just been given enough to make. Deliberately NOT a button: the orange button
   is the single primary action per page, and repeating it teaches the eye to
   ignore it. The rule above it does the separating, so it needs no box.
   The link is steel, never orange — the accent means the thing we catch. */
.cta-line {
  margin-top: 2.6rem; padding-top: 1.5rem;
  border-top: 1px solid var(--line);
  font-size: .95rem; color: var(--text-dim); max-width: 56ch;
}
.cta-line a {
  color: var(--text-head);
  border-bottom: 1px solid var(--line-strong);
  padding-bottom: 1px;
  transition: border-color .2s var(--spring), color .2s var(--spring);
}
.cta-line a:hover { color: var(--text); border-bottom-color: var(--accent-line); }
.cta-line--center { margin-inline: auto; text-align: center; }

/* Silkscreen fragments lifted from the brand boards. Flavour only. */
.silk {
  position: absolute;
  font-family: var(--mono); font-size: .6rem; line-height: 1.7;
  letter-spacing: .06em; color: var(--text-faint); opacity: .55;
  white-space: pre; pointer-events: none; user-select: none;
}
.silk--tr { top: 1.2rem; right: 0; text-align: right; }
.silk--bl { bottom: 1.2rem; left: 0; }
.silk em { font-style: normal; color: var(--accent); }
@media (max-width: 720px) { .silk { display: none; }
 }

/* ═══ HERO ════════════════════════════════════════════════════ */

.hero {
  position: relative;
  display: grid;
  /* Wider than the rest of the page on purpose. The hero is the only section carrying an object
     as well as text, and at the 1120px body width the wordmark could not hold one line while
     the mark drifted off on its own in the right half. */
  grid-template-columns: 1.06fr .94fr;
  gap: clamp(1.4rem, 2.6vw, 3rem);
  align-items: center;
  width: min(1260px, 100% - 3rem);
  margin-inline: auto;
  padding: clamp(2rem, 3.4vw, 3.2rem) 0 clamp(3.5rem, 6vw, 6rem);
}
.hero::before {                          /* the board, louder behind the phone */
  content: ""; position: absolute;
  top: -8rem; right: -18rem; bottom: -4rem; width: 74rem;
  background: var(--board) center / cover no-repeat;
  opacity: var(--hero-op);
  -webkit-mask-image: radial-gradient(62% 58% at 58% 48%, #000 28%, transparent 76%);
          mask-image: radial-gradient(62% 58% at 58% 48%, #000 28%, transparent 76%);
  pointer-events: none;
}
.hero::after {                           /* navy bloom lifting the phone off it */
  content: ""; position: absolute;
  top: 12%; right: -6rem; width: 40rem; height: 34rem;
  background: radial-gradient(closest-side, var(--card-glow), transparent 72%);
  pointer-events: none;
}

.hero__copy { position: relative; z-index: 1; }

/* ── The hero, as three groups rather than seven stacked lines ──────────────
   IDENTITY   pill · wordmark · descriptor      tight, it is one lockup
   GOAL       label · the sentence              set off by a rule, so it reads as a statement
   ACTION     explanation · buttons · note      the part that asks for something

   The spacing does the grouping: small gaps inside a group, a large one between groups. That
   is the whole composition, and it is why nothing here needs a box drawn around it. */

.hero__copy > * + * { margin-top: .55rem; }        /* the inside-a-group default */

/* The wordmark and its descriptor are set as ONE object of a fixed width. The wrapper is
   fit-content, so it takes the width of the headline; the descriptor is then a flex row at
   100% of that, spread with space-between. The two lines end on the same right edge at every
   viewport, with no measured letter-spacing to go stale when the type scale changes. */
.hero__lockup { width: fit-content; max-width: 100%; margin-top: 1.5rem; }

.hero__h1 {
  /* Sized so "Adapton — AI automation agency" holds ONE line at BOTH ends: the desktop column
     width, and every phone from 320px up. The vw term is fitted to measured one-line widths
     (17.5px at 320, 25px at 430) with about half a pixel of slack, so the line never wraps on a
     handset. Between roughly 470 and 1000 it wraps to two, and text-wrap:balance splits those
     evenly rather than dropping a single word onto its own line. */
  font-size: clamp(1rem, -0.3rem + 6.82vw, 2.55rem);
  font-weight: 640; line-height: 1.08; letter-spacing: -.03em;
  /* STEEL, not orange. The home page's h1 is the wordmark and takes the accent in home.css;
     every other h1 on the site is a sentence, and a three-line sentence set entirely in orange
     stops reading as a headline and starts reading as a warning label. Those pages put the
     accent on ONE span -- the turn in the sentence -- which is what .accent is for. */
  color: var(--text-head);
  text-wrap: balance;
}
.hero__h1 .accent { color: var(--accent); }

/* NOT stretched to the headline's width. Two attempts at that are worth recording so neither
   gets tried again: flex with space-between flushes both edges but cannot look even, because
   three words of different lengths make identical gaps read as three different gaps; fitted
   letter-spacing makes the texture even but opens the line so far it stops reading as a phrase
   and starts reading as a specimen.
   The descriptor is set at its own natural width. Its alignment is the LEFT EDGE — shared with
   the pill, the headline, the goal block, the paragraph and the buttons. That spine is a real
   alignment and it is the one this line belongs to. */
.hero__kicker {
  display: block;
  margin-top: .9rem;
  font-family: var(--mono);
  font-size: .8rem; font-weight: 500;
  letter-spacing: .2em; text-transform: uppercase;
  color: var(--text-dim);
}

/* The goal is a statement, not a third heading, so it gets a rule instead of a bigger size —
   the same orange rule the section eyebrows carry, turned on its side. */
.hero__goal-block {
  margin-top: 2.4rem;
  padding-left: 1.15rem;
  border-left: 2px solid var(--accent);
}
.hero__label {
  font-family: var(--mono);
  font-size: .8rem; font-weight: 600;
  letter-spacing: .22em; text-transform: uppercase;
  color: var(--accent);
}
.hero__goal {
  margin-top: .5rem;
  max-width: 26ch;
  font-size: clamp(1.2rem, 1.02rem + .7vw, 1.5rem);
  font-weight: 550; line-height: 1.34; letter-spacing: -.012em;
  color: var(--text-head);
}

.lede {
  max-width: 46ch;
  margin-top: 2.4rem;
  color: var(--text-dim);
  font-size: 1rem; line-height: 1.65;
}
/* js/home.js sets --lockup-w to the rendered width of the wordmark. The fallback keeps a sane
   measure if the script never runs. */
.hero__copy .lede,
.hero__copy .hero__cta { max-width: var(--lockup-w, 46ch); }
.lede b { color: var(--text); }

.hero__cta { display: flex; flex-wrap: wrap; gap: .8rem; margin-top: 1.7rem; }
.hero__cta .btn { flex: 1 1 14rem; }
.hero .note { margin-top: 1.1rem; }

.hero__copy > * { opacity: 0; transform: translateY(14px); }
html.is-ready .hero__copy > * {
  opacity: 1; transform: none;
  transition: opacity .55s ease, transform .55s var(--spring);
}
html.is-ready .hero__copy > :nth-child(2) { transition-delay: .06s; }
html.is-ready .hero__copy > :nth-child(3) { transition-delay: .12s; }
html.is-ready .hero__copy > :nth-child(4) { transition-delay: .18s; }
html.is-ready .hero__copy > :nth-child(5) { transition-delay: .24s; }

/* ═══ THE PHONE — the product, playing itself ═════════════════ */

/* the miss is grey and dashed — a broken connection, not an alarm */

/* ═══ MARQUEE RIBBON — the client lifecycle, on a loop ════════ */

/* ═══ SECTIONS ════════════════════════════════════════════════ */

.section { position: relative; padding: var(--gap) 0; }

/* Plain sections get one soft bloom off the leading edge, so they
   read as composed rather than as flat gaps between panels. */

.section--panel {
  background: color-mix(in srgb, var(--bg-panel) 88%, transparent);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

/* circuit-trace divider that draws itself on scroll */
.tracely { width: min(1120px, 100% - 2.5rem); margin: 0 auto; }
.tracely svg { width: 100%; height: 44px; display: block; }
.tracely path { fill: none; stroke-width: 1; stroke-dasharray: 100; stroke-dashoffset: 100; }
.tracely .t-base { stroke: var(--line-strong); }
.tracely .t-hot  { stroke: var(--accent-line); }
.tracely circle  { fill: var(--text-faint); }
.tracely rect    { fill: none; stroke: var(--text-faint); }
.tracely.draw path { transition: stroke-dashoffset 1.3s ease-out; stroke-dashoffset: 0; }
.tracely.draw .t-hot { transition-delay: .4s; }

/* ═══ CALCULATOR ══════════════════════════════════════════════ */

/* ═══ THE SEQUENCE — 01→05 down a single trace ════════════════ */

.track { position: relative; margin-top: 1.8rem; max-width: 46rem; }
.track::before {
  content: ""; position: absolute;
  left: 1.05rem; top: 1.4rem; bottom: 1.4rem; width: 1px;
  background: linear-gradient(180deg, var(--trace), var(--accent-line));
}

.step {
  position: relative;
  display: grid; grid-template-columns: 5.2rem 1fr;
  gap: clamp(1rem, 3vw, 2.4rem);
  padding: 1.75rem 1rem 1.75rem 0;
  border-radius: 10px;
  transition: background .22s ease;
}
.step + .step { border-top: 1px solid var(--line); }
.step:hover { background: color-mix(in srgb, var(--navy) 9%, transparent); }

.step__n {
  position: relative; z-index: 1;
  width: 2.1rem; height: 2.1rem;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--mono); font-size: .8rem; font-weight: 500;
  color: var(--text-head);
  background: var(--bg);
  border: 1px solid var(--line-strong);
  border-radius: 50%;
  transition: border-color .2s ease, color .2s ease, box-shadow .2s ease;
}
.step:hover .step__n {
  border-color: var(--orange); color: var(--accent);
  box-shadow: 0 0 0 4px var(--accent-soft);
}

.step__h {
  font-size: clamp(1.05rem, 1rem + .35vw, 1.22rem);
  font-weight: 600; letter-spacing: -.014em;
  margin-bottom: .4rem; color: var(--text);
}
.step__body p { color: var(--text-dim); max-width: 54ch; }
.step__body b { color: var(--accent); font-weight: 600; }

/* ═══ SEVEN DAYS + THE NIGHT SHIFT ════════════════════════════ */

/* ═══ PRICING ═════════════════════════════════════════════════ */

/* ═══ FAQ ═════════════════════════════════════════════════════ */

.faq { margin-top: 1.8rem; border-top: 1px solid var(--line); }

.qa { border-bottom: 1px solid var(--line); }
.qa summary {
  display: flex; align-items: center; justify-content: space-between; gap: 1.5rem;
  padding: 1.3rem 0;
  font-size: clamp(1.04rem, 1rem + .35vw, 1.22rem);
  font-weight: 560; letter-spacing: -.01em;
  color: var(--text-head);
  cursor: pointer; list-style: none;
  transition: color .16s ease;
}
.qa summary:hover { color: var(--text); }
.qa summary::-webkit-details-marker { display: none; }
.qa summary::after {
  content: "+"; font-family: var(--mono); font-size: 1.2rem;
  color: var(--accent); flex: none;
  transition: transform .22s var(--spring);
}
.qa[open] summary::after { transform: rotate(45deg); }
.qa > p { padding-bottom: 1.4rem; max-width: 60ch; color: var(--text-dim); font-size: .95rem; }

/* ═══ BOOK ════════════════════════════════════════════════════ */

/* Reserves its box so IntersectionObserver can see it — a display:none
   element has zero size and never intersects, so it would never load. */

/* ═══ FOOTER ══════════════════════════════════════════════════ */

.foot {
  border-top: 1px solid var(--line);
  background: var(--bg-panel);
  padding: 3.6rem 0 1.6rem;
  font-size: .92rem;
}

/* THE LOCKUP IS NOT REPEATED HERE. It sits in the sticky nav, which is on screen at the moment
   anyone reaches the footer, so a second copy is the same mark twice in one viewport. What the
   footer owes the reader is somewhere to go next, which is what the columns are for. */

.foot__grid {
  display: grid; gap: 2.4rem 2rem;
  grid-template-columns: 1.5fr repeat(3, 1fr);
}
@media (max-width: 980px) { .foot__grid { grid-template-columns: 1fr 1fr; gap: 2.2rem 1.6rem; } }
@media (max-width: 560px) { .foot__grid { grid-template-columns: 1fr; } }

.foot__blurb { color: var(--text-dim); margin: 0; max-width: 40ch; line-height: 1.62; }

/* Column headings. Orange and mono, matching the section eyebrows up the page -- the footer is
   the same document, so it uses the same signals rather than inventing footer-only ones. */
.foot__h {
  font-family: var(--mono); font-size: .78rem; font-weight: 600; letter-spacing: .16em;
  text-transform: uppercase; color: var(--accent);
  margin: 0 0 1rem;
}
.foot__h--gap { margin-top: 1.7rem; }

.foot__col ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .62rem; }
.foot__col a {
  color: var(--text-dim); text-decoration: none;
  transition: color .16s ease;
}
.foot__col a:hover { color: var(--text); }
.foot__col a:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 3px; }

.foot__contact li { display: flex; align-items: flex-start; gap: .6rem; color: var(--text-dim); }
.foot__ico { width: 16px; height: 16px; flex: none; margin-top: .22rem; color: var(--accent); }

/* Social icons. The markup is commented out in partials/foot.html until real accounts exist;
   these rules are here so switching it on is a one-line edit and not a design job. */
.foot__social { list-style: none; margin: 1.5rem 0 0; padding: 0; display: flex; gap: .6rem; }
.foot__social a {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; border-radius: 10px;
  border: 1px solid var(--line-strong); color: var(--text-dim);
  transition: color .16s ease, border-color .16s ease, background .16s ease;
}
.foot__social svg { width: 18px; height: 18px; }
.foot__social a:hover { color: var(--accent); border-color: var(--accent-line); background: var(--accent-soft); }

.foot__areas {
  margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .7rem 1.6rem;
}
.foot__areas-h {
  font-family: var(--mono); font-size: .74rem; font-weight: 600; letter-spacing: .16em;
  text-transform: uppercase; color: var(--text-faint); margin: 0;
}
.foot__areas ul {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: .5rem 1.4rem;
  color: var(--text-dim); font-size: .88rem;
}

.foot__bar {
  margin-top: 1.5rem; padding-top: 1.4rem; border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; justify-content: space-between; gap: .5rem 1.5rem;
  color: var(--text-faint); font-size: .85rem;
}
.foot__bar p { margin: 0; }
.foot__made { font-style: italic; }

/* ═══ RESPONSIVE ══════════════════════════════════════════════ */

@media (max-width: 900px) {
  .hero { grid-template-columns: 1fr; gap: 2.7rem; padding-top: 1.4rem; padding-bottom: calc(var(--gap) * .8); }
  .hero__copy { max-width: 34rem; }
  /* Sink the texture below the copy — on one column it sits directly
     under the headline and fights the reading. */
  .hero::before { top: auto; right: -26rem; bottom: -6rem; height: 60%; width: 60rem; opacity: calc(var(--hero-op) * .3); }
  .hero::after { top: auto; bottom: 0; right: -12rem; opacity: .7; }
  
  
  
  
  .nav__links { display: none; }
  .nav__in { gap: .8rem; }
  
  
     /* never fetched below 900px */
}

@media (max-width: 560px) {
  .step { grid-template-columns: 3rem 1fr; gap: .9rem; }
  .track::before { left: .95rem; }
  .step__n { width: 1.9rem; height: 1.9rem; font-size: .76rem; }
  .hero__kicker { font-size: .72rem; }
  .hero__cta { flex-direction: column; align-items: stretch; gap: .65rem; }
  /* flex: none is REQUIRED here. The desktop rule sets flex-basis 14rem to keep the two buttons
     an even pair in a ROW; once the container turns into a column that basis becomes a height,
     and each button grows to 224px tall. */
  .hero__cta .btn, .section--book .btn { width: 100%; flex: none; }
  .hero__h1 { line-height: 1.06; }
  .lede { margin-top: 1.2rem; }
  .hero__cta { margin-top: 1.6rem; }
  .note { margin-top: 1rem; }
  .foot__note { margin-right: 0; flex-basis: 100%; }
  
  
}

/* ═══ REDUCED MOTION — finished states, no theatre ════════════ */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
  .intro { display: none !important; }
  html.intro-play { overflow: auto; }
  
  .hero__copy > * { opacity: 1; transform: none; }
  
  
  .tracely path { stroke-dashoffset: 0; }
  
}

/* ═══ DEPTH — 3D tilt, cursor glare, board parallax ════════════
   Driven by js/core.js, pointer devices only, never under reduced motion.
   The glare is what stops the tilt reading as a gimmick: it implies a light source, so the
   card reads as a surface catching it rather than a rectangle being rotated. */

.has-depth {
  /* REQUIRED. ::after below is inset:0 against the nearest positioned ancestor. Without this
     the ancestor is .wrap, so every card's glare rendered as one 340px blob over the whole
     column instead of on the card under the cursor. Found 2026-08-19. */
  position: relative;
  transform: perspective(900px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
  transform-style: preserve-3d;
  transition: transform .35s var(--spring), border-color .25s ease;
  will-change: transform;
}
.has-depth::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  pointer-events: none; opacity: 0; transition: opacity .3s ease;
  background: radial-gradient(
    340px circle at var(--gx, 50%) var(--gy, 50%),
    color-mix(in srgb, var(--accent) 16%, transparent), transparent 60%);
}
.has-depth:hover::after { opacity: 1; }
.has-depth:hover { border-color: var(--accent-line); }

/* The board itself gains parallax. It is the brand, so it is the thing that gets depth. */
body::before { translate: var(--px, 0) var(--py, 0); transition: translate .5s cubic-bezier(.2,.8,.3,1); }

.btn { transition: transform .28s var(--spring), background-color .18s ease, border-color .18s ease, color .18s ease; }

/* ── Availability pill. Says one true, checkable thing at the top of the page. ── */
.pill {
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .4rem .8rem; margin-bottom: 1.4rem;
  border: 1px solid var(--line-strong); border-radius: 999px;
  background: var(--bg-card);
  font-family: var(--mono); font-size: .74rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--text-dim);
}
.pill::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft);
}

@media (prefers-reduced-motion: reduce) {
  .has-depth, .has-depth::after, body::before, .btn { transition: none; transform: none; translate: none; }
}

/* ═══ THE RIBBON — promoted from offer.css 2026-08-19 ═════════
   Both page types carry one now, so it lives here. Each page supplies its OWN sequence: the
   offer page runs a caller through the five automations, the home page runs the six pipeline
   stages. The orange items are what the system did; grey is what it started from. */

.ribbon {
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--navy) 8%, transparent);
  overflow: hidden; padding: .78rem 0;
}
.ribbon__track { display: flex; width: max-content; animation: ribbon 26s linear infinite; }
.ribbon:hover .ribbon__track { animation-play-state: paused; }
.ribbon__seq {
  display: flex; align-items: center;
  font-family: var(--mono); font-size: .8rem;
  letter-spacing: .18em; text-transform: uppercase;
  color: var(--text-head); white-space: nowrap;
}
.ribbon__seq > span { padding: 0 .9rem; }
.ribbon__seq .tick { color: var(--accent); padding: 0; }
.ribbon__seq .dim { color: var(--text-faint); }
@keyframes ribbon { to { transform: translateX(-50%); } }

/* The home page runs the STACK variant: the tools everything is built and run on, each as a
   name plus the one job it does. ONE FONT throughout — Space Grotesk at a single size, no mono
   and no uppercase. The only thing separating a name from its role is colour, which is what
   keeps thirteen entries reading as one clean line rather than a ransom note.
   NO ORANGE either: orange marks what Adapton catches or does, and a vendor list is neither,
   so the separators are hairline. Longest sequence on the site, so the track is slowed. */
.ribbon--stack .ribbon__seq {
  font-family: var(--font); font-size: .88rem; font-weight: 400;
  letter-spacing: .01em; text-transform: none;
}
.ribbon--stack .ribbon__track { animation-duration: 54s; }
.ribbon--stack .tool { display: inline-flex; align-items: baseline; gap: .45rem; }
.ribbon--stack .tool b { font-weight: 500; color: var(--text-head); }
.ribbon--stack .tool i { font-style: normal; color: var(--text-faint); }
.ribbon--stack .ribbon__seq .tick { color: var(--line-strong); padding: 0; }
@media (max-width: 560px) {
  .ribbon--stack .ribbon__track { animation-duration: 38s; }
  .ribbon--stack .tool i { display: none; }   /* names only — the roles will not fit */
}

/* ═══ CASE-STUDY CARDS — promoted from home.css 2026-08-19 ═════
   They appear on the home page AND on /work, so they belong here. They were in home.css,
   which /work does not load, and the work index rendered as a stack of underlined links.
   The rule that prevents a repeat: a component used by more than one page type lives in core. */

.cases {
  display: grid; gap: 1.1rem; margin-top: 1.8rem;
  grid-template-columns: repeat(auto-fit, minmax(272px, 1fr));
}
/* Eight studies, three up: two full rows and a row of two, which reads as a deliberate end
   rather than a gap. At the 1260 measure auto-fit fits four and strands the rest on a
   half-empty row — the same reason .team and .offers are fixed counts. */
@media (min-width: 900px) { .cases { grid-template-columns: repeat(3, 1fr); } }
.case {
  display: flex; flex-direction: column;
  padding: 1.6rem 1.5rem;
  background: var(--bg-card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  text-decoration: none;
  transition: border-color .25s, transform .25s var(--spring);
}
.case:hover { border-color: var(--accent-line); transform: translateY(-2px); }
/* The card's category, in the same orange mono as a section eyebrow and with the same rule in
   front of it. It is how you tell six cards apart at a glance, so it is not fine print. */
/* .case p sets --text-dim at (0,1,1) and .case__tag was (0,1,0), so its colour never applied
   and the tags rendered dim. Matched here so the declaration wins.
   Steel, not orange: the section name above is now a large orange line, and six orange tags
   under it would compete with it rather than support it. */
.case .case__tag {
  display: block;
  font-family: var(--mono); font-size: .82rem; font-weight: 600; letter-spacing: .14em;
  text-transform: uppercase; color: var(--text-head); margin-bottom: .85rem;
}
.case__h {
  font-size: 1.28rem; font-weight: 640; line-height: 1.28; letter-spacing: -.015em;
  color: var(--text-head); margin-bottom: .65rem;
}
.case p { color: var(--text-dim); font-size: .92rem; }

/* Two figures from the top of that case study's spec plate. They sit above the link rather than
   inside the paragraph because a number set in the middle of a sentence is read as prose and
   skipped; set apart with a rule above it, it is the thing that makes one card different from
   the seven beside it. STEEL, never orange -- see the rule on the plate itself. */
.case__figs {
  list-style: none; margin: 1.1rem 0 0; padding: .9rem 0 0;
  display: flex; flex-wrap: wrap; gap: 1.4rem;
  border-top: 1px solid var(--line);
}
.case__figs li { display: flex; flex-direction: column; gap: .15rem; max-width: 14ch; }
.case__figs b {
  font-size: 1.15rem; font-weight: 640; line-height: 1.1;
  color: var(--text-head); font-variant-numeric: tabular-nums;
}
.case__figs span {
  font-family: var(--mono); font-size: .54rem; line-height: 1.5;
  letter-spacing: .12em; text-transform: uppercase; color: var(--text-faint);
}

.case__go { margin-top: auto; padding-top: 1.1rem; font-weight: 600; font-size: .9rem; color: var(--accent); }
.case__go::after { content: " →"; }

/* ═══ CARD ART ─── the thumbnail on a case or offer card ══════════════════
   Three layers: a duotone photograph, a scrim, and a line schematic. The photograph gives the
   card depth; the schematic gives it an argument. A stock photo of a telephone tells a buyer
   nothing they could not guess -- "missed call -> 10s -> text back" is the actual pitch, and it
   is made here before anyone reads a word of the card.

   THE GROUND IS DARK IN BOTH THEMES, ON PURPOSE. Light line work needs something dark beneath
   it. The light-theme version of this -- pale duotone under a navy scrim -- washed out to a grey
   rectangle with a drawing floating on it. So only the -dark photograph is ever used and the
   colours below are FIXED VALUES rather than tokens: nothing about this component changes with
   the theme, which is also what lets one file (tools/card-art.mjs) feed the home page, /work and
   the offers from a single definition.

   The markup is mounted by tools/inject-card-art.mjs. Do not hand-write it into a page. */

.card-art {
  display: block; position: relative;
  margin: -.5rem 0 1.15rem;
  border-radius: 10px; overflow: hidden;
  aspect-ratio: 320 / 128;
  background: #0A0D14;                  /* shows for the instant before the photo decodes */
  isolation: isolate;
}
.card-art__photo { display: block; width: 100%; height: 100%; object-fit: cover; }

/* The scrim. Heavier at the foot, where the schematic's mono labels usually sit. */
.card-art::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(8, 11, 18, .30), rgba(8, 11, 18, .62));
}
.card-art .art {
  position: absolute; inset: 0; z-index: 1;
  display: block; width: 100%; height: 100%;
}

/* The drawing vocabulary. Fixed colours -- see the note above. The one exception to "fixed" is
   that these are all tuned against the scrimmed photograph, not against a flat panel, which is
   why the strokes sit brighter than the site's own line tokens. */
.card-art .node      { fill: rgba(10, 14, 22, .42); stroke: rgba(200, 214, 232, .42); stroke-width: 1.5; }
.card-art .ghost     { fill: none; stroke: rgba(200, 214, 232, .34); stroke-width: 1.3; stroke-dasharray: 4 3.5; }
.card-art .ghost-dot { fill: none; stroke: rgba(200, 214, 232, .34); stroke-width: 1.3; stroke-dasharray: 3 3; }

.card-art .wire       { fill: none; stroke: rgba(160, 190, 225, .78); stroke-width: 1.8; stroke-linecap: round; }
.card-art .wire--dash { stroke-dasharray: 5 4; }
.card-art .wire-fill  { fill: rgba(160, 190, 225, .78); stroke: none; }

.card-art .hair      { fill: none; stroke: #C8D4E4; opacity: .5; stroke-width: 1.4; stroke-linecap: round; }
.card-art .hair-fill { fill: #C8D4E4; opacity: .5; stroke: none; }
.card-art .head-fill { fill: #DCE4EE; stroke: none; }

/* The one orange element per card. Full brand orange: it is a shape on a dark ground in both
   themes, so it never needs the darkened --accent that orange TEXT needs on a white card. */
.card-art .hot-fill  { fill: var(--orange); stroke: none; }
.card-art .hot-line  { fill: none; stroke: var(--orange); stroke-width: 1.8; stroke-linecap: round; }
.card-art .hot-line--dash { stroke-dasharray: 5 4; }
.card-art .strike    { stroke: var(--orange); stroke-width: 2; stroke-linecap: round; }

.card-art .t {
  font-family: var(--mono); font-size: 9.5px; font-weight: 600;
  letter-spacing: .1em; fill: #B6C4D6;
}
.card-art .t--hot { fill: var(--orange); }

/* The card lifts on hover; the photograph lifts with it, very slightly, so the thumbnail reads
   as part of the card rather than a picture sitting on one. */
.case:hover .card-art__photo,
.offer:hover .card-art__photo { transform: scale(1.03); }
.card-art__photo { transition: transform .4s var(--spring); }

@media (prefers-reduced-motion: reduce) {
  .card-art__photo { transition: none; }
  .case:hover .card-art__photo, .offer:hover .card-art__photo { transform: none; }
}

/* Below the 3-up breakpoint a card goes full width and the 2.5:1 thumbnail becomes a band of
   photograph above three lines of text. The heading does the work at that size, so it goes. */
@media (max-width: 620px) { .card-art { display: none; } }

/* The offer card is one link, so the whole card is the target rather than a small line of
   text at the bottom of it. The inner "See the full offer" is a span, not a nested anchor. */
.offer { text-decoration: none; transition: border-color .25s, transform .25s var(--spring); }
.offer:hover { border-color: var(--accent-line); }
.offer:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
