/* ═══════════════════════════════════════════════════════════════════
   DAWNBREAKER DIGITAL — styles.css
   Edit any value here. All colours, sizes, spacing controlled here.
   ═══════════════════════════════════════════════════════════════════ */

:root {
  /* Motion. One curve and four durations, so timing is a decision made once
     rather than 26 hand-typed values that drifted apart. */
  --ease: cubic-bezier(0.25, 0.46, 0.45, 0.94);
  --ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);
  --t-1: 0.12s;   /* press, instant acknowledgement */
  --t-2: 0.2s;    /* hover on small controls */
  --t-3: 0.45s;   /* hover on large surfaces, fades */
  --t-4: 0.8s;    /* entrances */

  /* Colours */
  --bg: #080c12;
  --bg-elev: #0d121c;
  --fg: #e2e8f0;
  --fg-muted: #94a3b8;
  --fg-dim: #72839c;      /* lightened from #64748b: that was 4.12:1 on --bg, below WCAG AA 4.5:1 */
  --border: rgba(255, 255, 255, 0.08);
  --border-strong: rgba(255, 255, 255, 0.14);
  --blue: #3b82f6;
  --blue-deep: #1d4ed8;
  --blue-light: #93c5fd;

  /* Overwritten by script.js with the nav's measured height. This is the floor
     for the window before that runs, and for JS failing outright. Deliberately
     the LARGEST height the nav reaches rather than the most common one: too big
     lands an anchor slightly low, too small tucks it back under the nav, which
     is the failure this whole mechanism exists to remove. */
  --nav-h: 78px;
  --gold: #d4a574;        /* warm accent — used sparingly */
  --green: #22c55e;

  /* Type */
  --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  --font-serif: 'Newsreader', Georgia, serif;

  /* Layout */
  --container: 1100px;
  --container-narrow: 780px;
  --radius: 14px;
  --radius-sm: 8px;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

/* Measured, not derived: the nav is 78px as one row, 107.8px once it stacks
   into two, and 128.8-130.5px below about 393px, where "Start now" no longer
   fits beside the wordmark and its label wraps onto a second line. 131px is the
   worst case of the three. */
@media (max-width: 768px) {
  :root { --nav-h: 131px; }
}

html { scroll-behavior: smooth; }

/* Keeps the scrollbar's width reserved while the lightbox locks the page, so
   nothing shifts sideways when it opens. The page always scrolls, so nothing
   else changes. */
html { scrollbar-gutter: stable; }

body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

a { color: inherit; text-decoration: none; }
button { font-family: inherit; cursor: pointer; }
ul { list-style: none; }
img, svg { display: block; max-width: 100%; }

/* Subtle dawn glow at bottom, no animation */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background: radial-gradient(ellipse 80% 50% at 50% 110%,
              rgba(30, 58, 138, 0.28) 0%,
              rgba(59, 130, 246, 0.10) 40%,
              transparent 65%);
}

/* The gutter vignette lives here as well as on the sections, because having it
   on the sections ALONE was the visible colour step: the dark side bands began
   abruptly at section 01 while the hero above had none. This layer is fixed and
   viewport-wide, so the bands now start at the very top of the page and never
   change as you scroll past the hero boundary. It sits below the starfield, so
   the hero's stars still paint on top of it exactly as the sections' do. */
@media (min-width: 1280px) {
  body::before {
    --vig: calc((100% - var(--container)) / 2 + 90px);
    background-image:
      linear-gradient(to right,
      rgba(0, 0, 0, 0.46)  0,
      rgba(0, 0, 0, 0.38)  calc(var(--vig) * 0.35),
      rgba(0, 0, 0, 0.16)  calc(var(--vig) * 0.72),
      rgba(0, 0, 0, 0)     var(--vig),
      rgba(0, 0, 0, 0)     calc(100% - var(--vig)),
      rgba(0, 0, 0, 0.16)  calc(100% - var(--vig) * 0.72),
      rgba(0, 0, 0, 0.38)  calc(100% - var(--vig) * 0.35),
      rgba(0, 0, 0, 0.46)  100%),
      radial-gradient(ellipse 80% 50% at 50% 110%,
        rgba(30, 58, 138, 0.28) 0%,
        rgba(59, 130, 246, 0.10) 40%,
        transparent 65%);
  }
}

/* Starfield — actual 4-pointed sparkle shapes (white / gold / blue) in a 600px
   SVG tile. A real element, last in <body> where body::after used to paint,
   rather than a pseudo-element, so script.js can slide the tile with a
   transform. The compositor moves a transform without repainting; the old
   background-position parallax repainted the whole screen on every frame. The
   outer box holds the edge fade; the tile hangs one 600px period above it and
   slides down within that. */
.starfield {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  overflow: hidden;
  /* Mask fades only at very top/bottom to avoid hard borders */
  mask-image: linear-gradient(to bottom, transparent 0%, #000 5%, #000 95%, transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 5%, #000 95%, transparent 100%);
}

.starfield-tile {
  position: absolute;
  left: 0;
  right: 0;
  top: -600px;
  bottom: 0;
  background-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='600'%3E%3Cg fill='%23fff'%3E%3Cpath d='M90 116L91 119L94 120L91 121L90 124L89 121L86 120L89 119Z' opacity='.65'/%3E%3Cpath d='M485 72L486 74L488 75L486 76L485 78L484 76L482 75L484 74Z' opacity='.5'/%3E%3Cpath d='M165 460L166 464L170 465L166 466L165 470L164 466L160 465L164 464Z' opacity='.6'/%3E%3Cpath d='M520 487L521 489L523 490L521 491L520 493L519 491L517 490L519 489Z' opacity='.45'/%3E%3C/g%3E%3Cg fill='%23d4a574'%3E%3Cpath d='M240 196L241 199L244 200L241 201L240 204L239 201L236 200L239 199Z' opacity='.6'/%3E%3Cpath d='M420 377L421 379L423 380L421 381L420 383L419 381L417 380L419 379Z' opacity='.45'/%3E%3C/g%3E%3Cg fill='%2393c5fd'%3E%3Cpath d='M60 297L61 299L63 300L61 301L60 303L59 301L57 300L59 299Z' opacity='.5'/%3E%3Cpath d='M560 236L561 239L564 240L561 241L560 244L559 241L556 240L559 239Z' opacity='.6'/%3E%3C/g%3E%3Ccircle cx='300' cy='540' r='.7' fill='%23fff' opacity='.45'/%3E%3Ccircle cx='380' cy='100' r='.6' fill='%23d4a574' opacity='.5'/%3E%3Ccircle cx='130' cy='360' r='.5' fill='%2393c5fd' opacity='.4'/%3E%3C/svg%3E");
  background-size: 600px 600px;
  background-repeat: repeat;
  will-change: transform;
}

/* Film grain overlay — sits above all content, blended for tactile warmth.
   SVG turbulence filter encoded as data URI, no extra HTTP request. */
html::after {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 9998;
  background-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  opacity: 0.06;
  mix-blend-mode: overlay;
  /* Deliberately still. Grain is a property of the stock, not a video filter,
     and drifting it re-blended the whole viewport every two seconds for an
     effect its own comment admitted was barely perceptible. Translating a
     fixed, full-viewport layer by 2% also swept an ungrained strip along the
     top and right edges on every cycle. */
}

body > * { position: relative; z-index: 1; }

/* Focus styles for keyboard users */
a:focus-visible, button:focus-visible, summary:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 3px;
  border-radius: 4px;
}

/* ═══════════════════════════════════════════════════════════════════
   NAV
   ═══════════════════════════════════════════════════════════════════ */
.nav {
  position: sticky;
  top: 0;
  z-index: 100;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  padding: 18px 32px;
  background: rgba(8, 12, 18, 0.72);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--border);
}

.nav-brand { justify-self: start; }
.nav-links { justify-self: center; }
.nav > .btn-pill { grid-column: -2; justify-self: end; }
.nav-menu { display: none; }
.nav-links .nav-sheet-cta { display: none; }

.nav-brand {
  display: flex;
  align-items: center;
  gap: 12px;
}

/* A logo, not an instrument. It used to carry the hero's --beam so the dial
   could contract into this corner and hand over mid-turn, but the dial belongs
   to the hero now and a permanently rotating mark in the corner reads as a
   glitch rather than a readout. It holds still. */
.brand-shard {
  width: 30px;
  height: 30px;
  flex-shrink: 0;
}

.brand-text {
  white-space: nowrap;
  font-size: 14px;
  font-weight: 400;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--fg);
}

.brand-text-light {
  color: var(--blue-light);
  font-weight: 300;
}

/* ── The mark answers ──────────────────────────────────────────────────
   Dawn breaking, literally. The shard is seven blades at graded opacities,
   faintest at the outside, so instead of brightening the logo as one object
   each blade lifts on a 40ms delay from the spine outward and the light
   spreads. Nothing else on the page can do this, because nothing else is
   built out of blades.

   scale rather than transform: rotate: var(--beam) already owns this element,
   written every frame by script.js so the mark reports the bearing you have
   scrolled to. They are independent properties and compose, so it can swell
   without giving up its job. */
.brand-shard {
  transition: scale var(--t-2) var(--ease), filter var(--t-3) var(--ease);
}
.brand-shard polygon {
  transition: opacity var(--t-3) var(--ease);
}
.brand-text-light {
  transition: color var(--t-3) var(--ease);
}

@media (hover: hover) {
  .nav-brand:hover .brand-shard {
    scale: 1.09;
    filter: drop-shadow(0 0 8px rgba(125, 211, 252, 0.45))
            drop-shadow(0 0 20px rgba(125, 211, 252, 0.2));
  }

  /* Spine first, then in, mid, out. The <defs> block holds no polygons, so
     nth-of-type counts the blades in document order. */
  .nav-brand:hover .brand-shard polygon:nth-of-type(1) { opacity: 1; }
  .nav-brand:hover .brand-shard polygon:nth-of-type(2),
  .nav-brand:hover .brand-shard polygon:nth-of-type(5) { opacity: 0.94; transition-delay: 40ms; }
  .nav-brand:hover .brand-shard polygon:nth-of-type(3),
  .nav-brand:hover .brand-shard polygon:nth-of-type(6) { opacity: 0.78; transition-delay: 80ms; }
  .nav-brand:hover .brand-shard polygon:nth-of-type(4),
  .nav-brand:hover .brand-shard polygon:nth-of-type(7) { opacity: 0.58; transition-delay: 120ms; }

  /* The word lands on the shard gradient's own top stop. */
  .nav-brand:hover .brand-text-light { color: #e0f2fe; }
}

.nav-links {
  display: flex;
  gap: 32px;
  min-width: 0;
}

.nav-links a {
  font-size: 14px;
  color: var(--fg-muted);
  transition: color 0.2s;
}

.nav-links a:hover { color: var(--fg); }

.btn-pill {
  padding: 9px 22px;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  font-size: 14px;
  font-weight: 500;
  transition: background var(--t-2), border-color var(--t-2), transform var(--t-2);
}

.btn-pill:hover {
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.25);
}

/* ═══════════════════════════════════════════════════════════════════
   HERO
   ═══════════════════════════════════════════════════════════════════ */
.hero {
  position: relative;
  width: 100%;
  /* Deliberately a full 100vh even though the sticky nav sits above it, making
     the hero one nav taller than the screen. Subtracting the nav was tried and
     is worse: it shrinks the hero to 823px, the 880px constellation ring no
     longer fits inside it, and the top two words end up behind the nav. As it
     stands the ring clears the nav by 19px and only the descender of the lowest
     word reaches the fold. */
  min-height: 100vh;
  padding: 80px 32px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.hero-content {
  position: relative;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  max-width: 720px;
  margin: 0 auto;
}

/* ─── Hero dial: square container holding lines + constellation as a TRUE CIRCLE
     regardless of hero aspect ratio. Sized in vmin so it scales gracefully.
     Sized large so specs fall well outside the (narrower) content max-width. ─── */
/* ── Blue meteors ─────────────────────────────────────────────────────
   Thin streaks in the Dawnbreaker blues, angled 34deg below horizontal and
   travelling toward bottom-left. Motion is scroll-driven only: script.js
   mirrors scrollY into --scroll-y, and each meteor translates along its own
   streak axis by scrollY * --v. No timers, no keyframes — a static page shows
   static streaks, and the compositor handles the transform. The -0.829/0.559
   pair is cos/sin of the 34deg travel angle; keep them in sync with the
   rotate() below if the angle ever changes. */
/* One field for the page. z-index 0 puts it under every section, so a section
   only shows comets where it is see-through: the hero everywhere, 01 to 05 in
   their gutters, and the opaque cream panel at 06 nowhere.
   This used to exist five times over, once per section, each viewport-fixed.
   The moment the sections stopped being transformed, all five resolved to the
   screen at once and stacked into forty comets. One layer cannot do that. */
.page-comets {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}

.meteor {
  --v: 0.5;
  --len: 150px;
  --op: 0.4;
  position: absolute;
  width: var(--len);
  height: 1px;
  border-radius: 1px;
  background: linear-gradient(to right,
              transparent 0%,
              rgba(59, 130, 246, 0.35) 55%,
              rgba(147, 197, 253, 0.95) 100%);
  /* Scroll offset and resting angle live on the independent `translate` and
     `rotate` properties, which the browser applies BEFORE `transform`. That
     leaves `transform` free for the keyframes below, so the two motions
     compose instead of overwriting one another. Because `transform` is applied
     in the element's own rotated space, a plain translateX in the animation
     travels along the streak's own axis, which is the direction it points. */
  translate: calc(var(--scroll-y, 0px) * var(--v) * -0.829)
             calc(var(--scroll-y, 0px) * var(--v) *  0.559);
  rotate: var(--angle, 146deg);
  /* Faster for the nearer (higher --v) comets, and a negative delay so each one
     starts part way through its cycle rather than the whole field launching
     together on load. */
  animation: cometDrift calc(26s - var(--v) * 12s) linear infinite;
  animation-delay: calc(var(--v) * -19s);
  will-change: transform, opacity;
}

@keyframes cometDrift {
  0%   { transform: translateX(-340px); opacity: 0; }
  18%  { opacity: var(--op); }
  82%  { opacity: var(--op); }
  100% { transform: translateX(560px);  opacity: 0; }
}

/* Glowing head at the leading end of the streak */
.meteor::after {
  content: '';
  position: absolute;
  right: -1px;
  top: 50%;
  width: 3px;
  height: 3px;
  margin-top: -1.5px;
  border-radius: 50%;
  background: var(--blue-light);
  box-shadow: 0 0 8px 2px rgba(147, 197, 253, 0.55);
}

/* Absolute, not fixed. Fixed made the ring follow the viewport down the whole
   page, and it also broke the nav clearance: top: 47% then resolved against the
   viewport rather than the hero box, and the hero box starts one nav lower.
   Measured at 900px tall, that put the ring's top edge 17px ABOVE the viewport
   and the two upper words behind the nav. Anchored to the hero it clears by
   about 41px, which is what the 47% was chosen for in the first place. */
.hero-dial {
  --dial-size: clamp(880px, 85vmin, 1080px);
  position: absolute;
  /* 47% rather than dead centre: the sticky nav sits above the hero, so
     centring the ring on the hero box pushes the top two words up under it.

     The max() is a floor under that, because 47% alone is only safe on a tall
     screen. --dial-size has an 880px minimum while the hero is 100vh, so the
     shorter the viewport the further 47% of it drifts up. Measured: 19px of
     clearance at 900px tall, 4.8px at 870px, and 0.6px at 861px -- the shortest
     height where the ring is shown at all. It was one pixel from collision and
     nothing in the file said so.

     The second term is where the centre HAS to be for the top word to clear.
     Those words sit 0.47 x cos(22.5deg) = 0.4342 of the ring above its centre,
     and their text box reaches a further 22px above that (measured, not
     derived: it depends on the font). +42px therefore buys a 20px gap. It
     resolves to 424px, so 47% wins from about 903px upward and taller screens
     keep the geometry they had; below that the ring drops instead of climbing
     and is clipped at the BOTTOM, which is past the fold anyway. */
  top: max(47%, calc(var(--dial-size) * 0.4342 + 42px));
  left: 50%;
  width: var(--dial-size);
  height: var(--dial-size);
  transform: translate(-50%, -50%);
  pointer-events: none;
  z-index: 0;
}

.hero-lines {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.hero-lines line {
  stroke: rgba(147, 197, 253, 0.18);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
  stroke-linecap: round;
  stroke-dasharray: 2000;
  stroke-dashoffset: 2000;
  animation: lineDraw 1.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
}

@keyframes lineDraw {
  to { stroke-dashoffset: 0; }
}

/* Vary opacity, dash pattern, delay per line for hand-drawn feel */
.hero-lines .line-1  { stroke: rgba(212, 165, 116, 0.20); stroke-dasharray: 3 2, 2000; animation-delay: 0.6s; }
.hero-lines .line-2  { stroke: rgba(147, 197, 253, 0.16); animation-delay: 0.72s; }
.hero-lines .line-3  { stroke: rgba(255, 255, 255, 0.10); stroke-dasharray: 1 3, 2000; animation-delay: 0.84s; }
.hero-lines .line-4  { stroke: rgba(212, 165, 116, 0.14); animation-delay: 0.96s; }
.hero-lines .line-5  { stroke: rgba(147, 197, 253, 0.18); stroke-dasharray: 4 3, 2000; animation-delay: 1.08s; }
.hero-lines .line-6  { stroke: rgba(255, 255, 255, 0.08); animation-delay: 1.20s; }
.hero-lines .line-7  { stroke: rgba(212, 165, 116, 0.18); stroke-dasharray: 2 4, 2000; animation-delay: 1.32s; }
.hero-lines .line-8  { stroke: rgba(147, 197, 253, 0.14); animation-delay: 1.44s; }

/* Constellation container — fills the square dial */
.hero-constellation {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  user-select: none;
}

/* Each spec is positioned on a TRUE CIRCLE using its --angle.
   --dial-r is the radius as a fraction of the dial's half-size.
   Position formula: top = 50% - r*cos(angle), left = 50% + r*sin(angle). */
.hero-constellation .spec {
  --lit: 0;
  --dial-r: 47%;
  position: absolute;
  top:  calc(50% - var(--dial-r) * cos(var(--angle)));
  left: calc(50% + var(--dial-r) * sin(var(--angle)));
  white-space: nowrap;
  opacity: calc(0.5 + var(--lit) * 0.5);
  filter: brightness(calc(1 + var(--lit) * 1));
  text-shadow:
    0 0 calc(32px * var(--lit)) rgba(212, 165, 116, calc(0.8 * var(--lit))),
    0 0 calc(10px * var(--lit)) rgba(255, 255, 255, calc(0.5 * var(--lit)));
  transform: translate(-50%, -50%) rotate(var(--rot, 0deg));
  /* No transitions — JS updates every frame, transitions would just fight it */
  will-change: opacity, filter;
}

.hero-constellation .spec em { font-style: italic; }

/* Each spec: typographic treatment + slight rotation, highlighted in turn as the
   shard sweeps past. Eight specs at an equal 45° spacing, offset 22.5° from north,
   so the gaps stay centred on 0° and 180° where the headline and buttons sit.
   With the 30s shard rotation that puts the peaks 3.75s apart.
   The angles themselves live on the elements in index.html; script.js reads them
   back, so they are not duplicated here. */
.hero-constellation .spec-1 {
  font-family: var(--font-serif);
  font-size: 1.7rem;
  font-weight: 400;
  color: var(--fg);
  --rot: -2deg;
}
.hero-constellation .spec-2 {
  font-family: var(--font-sans);
  font-size: 0.95rem;
  font-weight: 300;
  color: var(--fg-muted);
  text-transform: lowercase;
  --rot: 1deg;
}
.hero-constellation .spec-3 {
  font-family: var(--font-sans);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.32em;
  color: var(--gold);
  text-transform: uppercase;
  --rot: -3deg;
}
.hero-constellation .spec-4 {
  font-family: var(--font-serif);
  font-size: 1.45rem;
  font-weight: 400;
  color: var(--blue-light);
  --rot: 2deg;
}
.hero-constellation .spec-5 {
  font-family: var(--font-sans);
  font-size: 1.05rem;
  font-weight: 400;
  color: var(--fg-muted);
  text-transform: lowercase;
  --rot: -1deg;
}
.hero-constellation .spec-6 {
  font-family: var(--font-sans);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.42em;
  color: var(--gold);
  text-transform: uppercase;
  --rot: 3deg;
}
.hero-constellation .spec-7 {
  font-family: var(--font-serif);
  font-size: 1.55rem;
  font-weight: 400;
  color: var(--fg);
  --rot: -2deg;
}
.hero-constellation .spec-8 {
  font-family: var(--font-sans);
  font-size: 0.65rem;
  font-weight: 400;
  letter-spacing: 0.4em;
  color: rgba(255, 255, 255, 0.45);
  text-transform: uppercase;
  --rot: 1deg;
}

/* Hide the constellation when there is not room for it — horizontally OR
   vertically. On a short viewport the two constraints cannot both be met: the
   ring has to be wide enough that the side words clear the centred copy, but
   that same width pushes the top words up behind the sticky nav. Measured at
   1180x800 no radius satisfies both, so the ring is dropped rather than shown
   colliding. The shard and starfield still carry the hero. */
@media (max-width: 1100px), (max-height: 860px) {
  .hero-constellation,
  .hero-lines { display: none; }
}

/* Aurora shard as "searcher" — anchored centre, rotates 360° infinitely.
   Specimens are highlighted in sync as the shard's north point passes them.
   Stacking: .hero-dial (z 0, own stacking context: lines then specimens)
   < shard (z 1) < content (z 3). The shard therefore paints over the spec
   words where they overlap; it is translucent, and the effect is deliberate. */
/* Fixed rather than absolute so the instrument can leave the hero and dock
   into the nav. `.hero` has no transform, so a fixed descendant escapes its
   overflow:hidden and stops scrolling away with it.
   The beam angle is owned by JS and rides at the END of this transform list.

   It must NOT move to the independent `rotate` property, which is what .meteor
   does. The browser applies translate/rotate/scale BEFORE `transform`, so the
   whole list below would then be evaluated in the rotated frame -- including
   the `translate(-50%, -50%)` that centres this element. The centering offset
   would orbit the anchor point as the beam swept, throwing the shard hundreds
   of pixels off centre. The trick is safe on .meteor only because its
   `transform` carries keyframe motion and no percentage centering. */
.hero-shard {
  position: absolute;
  top: 45%;
  left: 50%;
  width: clamp(420px, 50vmin, 660px);
  height: auto;
  pointer-events: none;
  z-index: 1;
  opacity: 0;
  filter: blur(0.5px) drop-shadow(0 0 18px rgba(147, 197, 253, 0.18));
  transform: translate(-50%, -50%) rotate(var(--beam, 0deg));
  animation: shardConstruct 2.6s ease 0.25s forwards;
  transform-origin: center center;
}

/* The mark builds itself when the page opens: it comes up brighter than its
   resting level while the seven blades arrive, then settles back to the faint
   backdrop behind the headline. Plays once. */
@keyframes shardConstruct {
  0%   { opacity: 0; }
  38%  { opacity: 0.5; }
  62%  { opacity: 0.5; }
  100% { opacity: 0.13; }
}

/* Spine first, then each pair of blades from the inside out, each sliding in
   from a little further away. --dx / --dy are in the SVG's own units. */
.hero-shard polygon {
  transform-box: fill-box;
  transform-origin: center;
  animation: shardBladeIn 1.05s cubic-bezier(.2, .7, .2, 1) backwards;
}
@keyframes shardBladeIn {
  from { opacity: 0; transform: translate(var(--dx), var(--dy)); }
}
.hero-shard polygon:nth-of-type(1) { --dx: 0px;   --dy: -26px; animation-delay: 0.30s; }
.hero-shard polygon:nth-of-type(2) { --dx: -16px; --dy: 8px;   animation-delay: 0.46s; }
.hero-shard polygon:nth-of-type(5) { --dx: 16px;  --dy: 8px;   animation-delay: 0.46s; }
.hero-shard polygon:nth-of-type(3) { --dx: -30px; --dy: 14px;  animation-delay: 0.62s; }
.hero-shard polygon:nth-of-type(6) { --dx: 30px;  --dy: 14px;  animation-delay: 0.62s; }
.hero-shard polygon:nth-of-type(4) { --dx: -46px; --dy: 20px;  animation-delay: 0.78s; }
.hero-shard polygon:nth-of-type(7) { --dx: 46px;  --dy: 20px;  animation-delay: 0.78s; }

/* shardSearch is gone: the sweep is driven by --beam from script.js, so the
   same angle can free-run in the hero and then seek a per-section bearing. */

.hero-headline {
  font-family: var(--font-serif);
  font-size: clamp(2.2rem, 5vw, 3.6rem);
  font-weight: 400;
  line-height: 1.12;
  letter-spacing: -0.02em;
  color: var(--fg);
  margin-bottom: 28px;
}

/* Hero text mask reveal — plays once on load */
.hero-headline .line {
  display: block;
  overflow: hidden;
  padding-bottom: 0.05em;  /* prevent descenders from clipping */
}

.hero-headline .line-inner {
  display: inline-block;
}

/* Entrance animation is opt-in: the `anim` class is only set when the page
   loads visible. Resting state stays readable so a background tab, a print,
   or a browser with JS off still shows a complete hero. */
.anim .hero-headline .line-inner {
  transform: translateY(110%);
  animation: lineReveal 0.85s cubic-bezier(0.5, 0.05, 0.2, 1) forwards;
}

.anim .hero-headline .line:nth-child(1) .line-inner { animation-delay: 0.15s; }
.anim .hero-headline .line:nth-child(2) .line-inner { animation-delay: 0.32s; }

@keyframes lineReveal {
  to { transform: translateY(0); }
}

/* Subtitle + CTA fade up after headline */
.anim .fade-up {
  opacity: 0;
  transform: translateY(16px);
  animation: fadeUp 0.7s ease forwards;
}

/* Not 0: a browser does not count fully transparent text as painted. */
.anim .hero-sub.fade-up { opacity: 0.01; animation-delay: 0.65s; }
.anim .hero-cta.fade-up { animation-delay: 0.85s; }
.anim .hero-guarantee.fade-up { animation-delay: 1.05s; }

@keyframes fadeUp {
  to { opacity: 1; transform: translateY(0); }
}


.italic-blue {
  font-style: italic;
  color: var(--blue);
  font-weight: 400;
}

/* Keep the emphasised phrase on one visual line. At 375px "systems do" was
   breaking after "systems", which splits the one highlight in the headline
   across two lines and reads as a mistake. The phrase is far narrower than the
   column at every size, so this cannot cause overflow. */
.hero-headline .italic-blue { white-space: nowrap; }

.hero-sub {
  max-width: 560px;
  font-size: 1.05rem;
  line-height: 1.6;
  color: var(--fg-muted);
  margin-bottom: 36px;
}

.hero-sub span {
  display: block;
  margin-bottom: 0.7em;
}

.hero-sub span:last-child {
  margin-bottom: 0;
}

.hero-cta {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  justify-content: center;
}

/* Buttons */
.btn-primary, .btn-ghost {
  padding: 13px 26px;
  border-radius: 999px;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.01em;
  transition: background 0.2s, border-color 0.2s, color 0.2s, transform 0.2s;
  display: inline-block;
}

.btn-primary {
  background: #2563eb;
  color: #fff;
  border: 1px solid #2563eb;
}

.btn-primary:hover {
  background: #1d4ed8;
  border-color: #1d4ed8;
}

.btn-ghost {
  background: transparent;
  color: var(--fg);
  border: 1px solid var(--border-strong);
}

.btn-ghost:hover {
  border-color: rgba(255, 255, 255, 0.3);
  background: rgba(255, 255, 255, 0.03);
}

/* ═══════════════════════════════════════════════════════════════════
   SECTIONS — shared
   ═══════════════════════════════════════════════════════════════════ */
/* Full-bleed background, centred content. Setting max-width here instead
   clipped every section's background colour to a 1100px band. */
.section {
  position: relative;
  width: 100%;
  /* Held in a variable so the giant numeral can sit on the same edge as the
     copy without repeating the expression. */
  --pad-x: max(32px, calc((100% - var(--container)) / 2));
  /* How far the numeral hangs below the section's top edge. A variable because
     the anchor offset below has to know it. */
  --num-top: 10px;
  padding: 100px var(--pad-x);

  /* Where a nav link lands you. Written as a rule rather than a number: the
     numeral's TOP EDGE lands 40px below the nav, whatever the nav currently
     measures. That matters because the nav is not a fixed height: 78px as one
     row, 107.8px once it stacks into two, and about 131px below 393px where the
     "Start now" label wraps. The numeral itself also sits lower at that width. Landing at y=0, which is what no
     scroll-margin means, put the numeral and the frame's rounded top edge
     underneath the nav at every width. --nav-h is measured in script.js; the
     fallback is the desktop height. */
  scroll-margin-top: calc(var(--nav-h, 78px) + 40px - var(--num-top));
}

.section-narrow {
  --pad-x: max(32px, calc((100% - var(--container-narrow)) / 2));
  padding-left: var(--pad-x);
  padding-right: var(--pad-x);
}

.section > * { position: relative; z-index: 1; }

/* Section headings that carry no visual weight. The eyebrow is the
   visible label, but every section still needs a real heading so the document
   outline holds up for screen readers and search engines. */
.section-title-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.section-eyebrow {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 18px;
}

.section-eyebrow .dash {
  width: 24px;
  height: 1px;
  background: var(--gold);
  flex-shrink: 0;
}

.section-eyebrow span:not(.dash) {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.18em;
  color: var(--fg-muted);
  text-transform: uppercase;
}

.section-title {
  font-family: var(--font-serif);
  font-size: clamp(2rem, 4vw, 2.8rem);
  font-weight: 400;
  letter-spacing: -0.02em;
  color: var(--fg);
  margin-bottom: 56px;
  line-height: 1.15;
}

/* Headings arrive with the hero's masked rise instead of fading in with
   everything else, so each section opens on a beat.
   Two details that are not optional:
   travel is 1.3em rather than the hero's 110%, because a heading that wraps to
   two lines would otherwise move at double velocity; and .section-title is
   excluded from the blanket child fade below, or it would carry a fade AND a
   rise at once, which reads as a mistake rather than a flourish. */
.section-title .line-inner {
  display: inline-block;
  will-change: transform;
}
.reveal-on-scroll .section-title {
  overflow: hidden;
  padding-bottom: 0.08em;   /* descenders clip against overflow:hidden */
}
.reveal-on-scroll .section-title .line-inner {
  transform: translateY(1.3em);
}
.reveal-on-scroll.in-view .section-title .line-inner {
  transform: translateY(0);
  transition: transform 0.85s cubic-bezier(0.5, 0.05, 0.2, 1);
}

/* ═══════════════════════════════════════════════════════════════════
   SERVICES — editorial / manifesto layout, no cards
   ═══════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════
   WORK
   ═══════════════════════════════════════════════════════════════════ */
/* ═══════════════════════════════════════════════════════════════════
   WORK — browser-frame mockups, asymmetric layout
   ═══════════════════════════════════════════════════════════════════ */

/* Browser frame */
.browser {
  background: #1a1f2e;
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
  transition: border-color 0.3s ease, box-shadow 0.4s ease;
}

@media (hover: hover) {
  .prod-demo:hover .browser {
  border-color: rgba(212, 165, 116, 0.35);
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(212, 165, 116, 0.15);
}
}

.browser-body {
  position: relative;
  aspect-ratio: 16 / 10;
  background: linear-gradient(135deg, #1e3a8a, #1d4ed8 50%, #3b82f6);
  overflow: hidden;
}

/* Live iframe preview. Each frame renders at its own virtual width (data-fw)
   and is scaled down to fit, so narrow apps crop tight instead of sitting in
   empty margins. Width, height and --thumb-scale are all set by JS. */
.work-frame {
  position: absolute;
  top: 0; left: 0;
  border: none;
  transform-origin: top left;
  pointer-events: none;
  background: #fff;
}

/* Thumbnail scaling.
   scale() needs a unitless number and CSS cannot convert px to a number,
   so calc(100cqw / 1500) silently collapses. --thumb-scale is set by JS
   in index.html; the fallback keeps something sensible without it. */
.browser-body { contain: layout paint; }

.work-frame {
  transform: scale(var(--thumb-scale, 0.34));
}

/* Hover overlay invitation */
.work-frame-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 16px;
  background: linear-gradient(to top, rgba(8, 12, 18, 0.6) 0%, transparent 50%);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  opacity: 0;
  transition: opacity 0.3s ease;
  cursor: pointer;
  text-decoration: none;
}

/* Hover-only reveal is fine with a pointer and useless without one: on a phone
   these five live demos looked like flat screenshots. Scoped to devices that
   actually hover, with a permanent scrim for the rest. */
@media (hover: hover) {
  .prod-demo:hover .work-frame-overlay,
  .work-frame-overlay:focus-visible {
    opacity: 1;
  }
}

@media (hover: none) {
  /* Always legible, but lighter than the hover treatment so it reads as a
     label on the frame rather than chrome bolted over it. */
  .work-frame-overlay {
    opacity: 1;
    background: linear-gradient(to top, rgba(8, 12, 18, 0.72) 0%, transparent 42%);
  }
}

/* overflow:hidden on the frame was clipping this focus ring out of existence. */
.work-frame-overlay:focus-visible {
  opacity: 1;
  outline: 2px solid var(--blue-light);
  outline-offset: -4px;
}

/* ═══════════════════════════════════════════════════════════════════
   PRICING
   ═══════════════════════════════════════════════════════════════════ */

.btn-tier {
  display: block;
  width: 100%;
  padding: 13px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: transparent;
  color: var(--fg);
  font-size: 14px;
  font-weight: 600;
  text-align: center;
  letter-spacing: 0.02em;
  transition: background var(--t-2), border-color var(--t-2), transform var(--t-2);
}

.btn-tier:hover {
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.25);
}

/* ═══════════════════════════════════════════════════════════════════
   PRODUCTS — one long page, every detail inline
   ═══════════════════════════════════════════════════════════════════ */
/* minmax(0,1fr) + min-width:0 stop grid items refusing to shrink below their
   content width, which pushed the whole page into horizontal scroll on phones. */
.prod-list {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 110px;
}

.prod { min-width: 0; }

.prod { scroll-margin-top: calc(var(--nav-h, 78px) + 24px); }

.prod-head {
  display: flex;
  align-items: baseline;
  gap: 20px;
  padding-bottom: 22px;
  margin-bottom: 30px;
  border-bottom: 1px solid var(--border);
}

/* The product number is the only numbering left on the page, so it is sized
   to mark each product as a new one while scrolling past. */
.prod-index {
  font-family: var(--font-serif);
  font-size: clamp(3.4rem, 7vw, 5.6rem);
  font-weight: 300;
  line-height: 0.8;
  color: var(--gold);
  flex-shrink: 0;
  letter-spacing: -0.03em;
  font-variant-numeric: lining-nums;
}

.prod-title {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: clamp(2.1rem, 5vw, 3.4rem);
  line-height: 1.02;
  letter-spacing: -0.025em;
  color: var(--fg);
  /* Two-line names on a phone break evenly, not "Digital Punch / Card". */
  text-wrap: balance;
}

.prod-sub {
  margin-top: 8px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--fg-dim);
}

.prod-copy { max-width: 74ch; }
.prod-copy p { color: var(--fg-muted); line-height: 1.78; margin-bottom: 18px; }
.prod-copy p b { color: var(--fg); font-weight: 600; }

.prod-points {
  margin: 24px 0 0;
  columns: 2;
  column-gap: 34px;
}

.prod-points li {
  break-inside: avoid;
  margin-bottom: 10px;
}

.prod-points li {
  position: relative;
  padding-left: 20px;
  color: var(--fg-muted);
  font-size: 14.5px;
  line-height: 1.55;
}

.prod-points li::before {
  content: '';
  position: absolute;
  left: 2px;
  top: 9px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--blue);
}

/* ── Split: reading column beside a sticky demo ── */
.prod-split {
  display: grid;
  grid-template-columns: 0.92fr 1.08fr;
  gap: 44px;
  align-items: start;
}

.prod-read { min-width: 0; }
.prod-read .prod-copy { max-width: none; }

.prod-aside { min-width: 0; position: sticky; top: 92px; }
.prod-aside .prod-demo { margin-top: 0; }

/* ── Demos ── */
.prod-demo { margin-top: 34px; }

.prod-demo-cap {
  margin-top: 12px;
  font-size: 12.5px;
  color: var(--fg-dim);
  letter-spacing: 0.03em;
}

.prod-demo-cap b { color: var(--fg-muted); font-weight: 600; }

.prod-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 26px;
  margin-top: 34px;
}

.prod-pair .prod-demo { margin-top: 0; }

/* A phone view of each sample site, laid over the bottom-right corner of its
   desktop frame. It ignores the pointer, so the frame's "Open it" link still
   works underneath. */
.prod-pair .prod-demo { position: relative; }

.site-phone {
  position: absolute;
  right: -8px;
  bottom: 14px;
  z-index: 2;
  width: 22%;
  max-width: 130px;
  height: auto;
  border: 5px solid #0d0f14;
  border-radius: 18px;
  background: #0d0f14;
  box-shadow: 0 16px 34px rgba(0, 0, 0, 0.5);
  pointer-events: none;
}


/* ── Honest limits ── */


/* ── Per-product footer ── */
/* The time (setup or build) is the one thing here in the accent colour, on
   its own tinted panel, so it cannot be read as more body text. The closing
   bar of the Pricing section (.pricing-ask) is the same panel. */
.prod-foot,
.pricing-ask {
  display: flex;
  align-items: center;
  gap: 22px;
  flex-wrap: wrap;
  margin-top: 34px;
  padding: 20px 24px;
  border: 1px solid rgba(212, 165, 116, 0.34);
  border-radius: var(--radius);
  background: rgba(212, 165, 116, 0.07);
  transition: border-color var(--t-2) var(--ease), background-color var(--t-2) var(--ease), box-shadow var(--t-2) var(--ease);
}

.prod-time,
.pricing-ask-t {
  font-family: var(--font-serif);
  font-size: 1.6rem;
  line-height: 1.2;
  color: var(--gold);
  transition: color var(--t-2) var(--ease);
}

/* With a mouse, the bar lights up under the pointer. Touch screens have no
   hover, and a highlight that stuck after a tap would look like a fault. */
@media (hover: hover) {
  .prod-foot:hover,
  .pricing-ask:hover {
    border-color: rgba(212, 165, 116, 0.78);
    background: rgba(212, 165, 116, 0.13);
    box-shadow: 0 0 0 1px rgba(212, 165, 116, 0.18), 0 14px 40px -22px rgba(212, 165, 116, 0.6);
  }
  .prod-foot:hover .prod-time,
  .pricing-ask:hover .pricing-ask-t { color: #f1c796; }
}

/* The Custom System's time needs its qualifier under it. */
.prod-terms { display: grid; gap: 5px; }
.prod-note { font-size: 13px; color: var(--fg-muted); }
.prod-foot .btn-tier,
.pricing-ask .btn-tier { width: auto; margin-left: auto; padding: 12px 26px; }

@media (max-width: 980px) {
  .prod-split { grid-template-columns: 1fr; gap: 26px; }
  .prod-aside { position: static; }
}

@media (max-width: 860px) {
  .prod-list { gap: 76px; }
  .prod-pair { grid-template-columns: 1fr; gap: 20px; }
  .prod-head { gap: 14px; margin-bottom: 22px; }
  .prod-foot .btn-tier,
  .pricing-ask .btn-tier { margin-left: 0; width: 100%; text-align: center; }
}

/* ═══════════════════════════════════════════════════════════════════
   HOW PRICING WORKS — the terms in words: the explanation on the left,
   the four short terms down the right
   ═══════════════════════════════════════════════════════════════════ */
.pricing-how {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg-elev);
}

.pricing-how-main { padding: 30px 32px 32px; }

.pricing-how-main h3 {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: clamp(1.3rem, 2.6vw, 1.7rem);
  margin-bottom: 14px;
}

.pricing-how-main p {
  max-width: 60ch;
  color: var(--fg-muted);
  font-size: 15px;
  line-height: 1.75;
  text-wrap: pretty;
}

/* The key sentence, set brighter and heavier so it reads first. */
.pricing-how-main .pricing-how-key {
  margin-top: 16px;
  color: var(--fg);
  font-weight: 500;
}

.pricing-how-facts {
  display: grid;
  align-content: center;
  padding: 14px 32px;
  border-left: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.02);
}

.pricing-how-facts li {
  display: flex;
  align-items: baseline;
  gap: 14px;
  padding: 13px 0;
  border-top: 1px solid var(--border);
  font-family: var(--font-serif);
  font-size: 1.2rem;
  line-height: 1.3;
  color: var(--fg);
}

.pricing-how-facts li:first-child { border-top: none; }

/* A shorter version of the gold rule in the section eyebrows. */
.pricing-how-facts li::before {
  content: '';
  flex: 0 0 14px;
  height: 1px;
  background: var(--gold);
  transform: translateY(-0.32em);
}

/* Closing bar of the section; the panel is shared with .prod-foot above. */
.pricing-ask { margin-top: 44px; }

@media (max-width: 780px) {
  .pricing-how { grid-template-columns: minmax(0, 1fr); }
  .pricing-how-main { padding: 22px 20px 24px; }
  .pricing-how-facts {
    padding: 6px 20px;
    border-left: none;
    border-top: 1px solid var(--border);
  }
  .pricing-ask { margin-top: 32px; }
}

/* ═══════════════════════════════════════════════════════════════════
   SYSTEM RECOMMENDER  (replaced the leak calculator; its .calc* and
   .leak-tab* rules went with it)
   ═══════════════════════════════════════════════════════════════════ */
.rec {
  margin-top: 64px;
  padding: 34px 36px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: linear-gradient(160deg, rgba(59,130,246,0.06), rgba(8,12,18,0) 60%), var(--bg-elev);
}

.rec-head h3 {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: clamp(1.4rem, 3vw, 1.9rem);
  letter-spacing: -0.01em;
  margin-bottom: 8px;
}

.rec-qs { margin-top: 28px; display: grid; gap: 24px; }

/* min-width: 0 because a fieldset refuses to shrink below its content in a
   grid item, which pushes the page into horizontal scroll on a phone. */
.rec-q { border: 0; padding: 0; margin: 0; min-width: 0; }

.rec-q-h {
  padding: 0;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-dim);
  font-weight: 600;
}

.rec-opts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }

.rec-opt { position: relative; display: inline-flex; }

/* Kept in the tab order and still focusable; only the dot is hidden. */
.rec-opt input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  margin: 0;
  pointer-events: none;
}

.rec-opt span {
  display: block;
  padding: 12px 16px;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.02);
  color: var(--fg-muted);
  font-size: 14px;
  line-height: 1.3;
  cursor: pointer;
  transition: border-color var(--t-2) var(--ease),
              background var(--t-2) var(--ease),
              color var(--t-2) var(--ease);
}

.rec-opt:hover span { border-color: rgba(255, 255, 255, 0.28); color: var(--fg); }

.rec-opt input:checked + span {
  border-color: var(--blue);
  background: rgba(59, 130, 246, 0.14);
  color: var(--fg);
}

.rec-opt input:focus-visible + span { outline: 2px solid var(--blue); outline-offset: 2px; }

.rec-out {
  margin-top: 30px;
  padding-top: 24px;
  border-top: 1px solid var(--border);
}

.rec-wait { color: var(--fg-dim); font-size: 14px; }

.rec-picks {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: 14px;
}

.rec-pick {
  padding: 20px 22px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, 0.02);
  animation: rec-in 0.4s var(--ease-out) both;
}

.rec-pick:nth-child(2) { animation-delay: 0.08s; }

@keyframes rec-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

.rec-pick-ord {
  display: block;
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 10px;
}

.rec-pick-name {
  display: block;
  font-family: var(--font-serif);
  font-size: 1.25rem;
  line-height: 1.25;
}

.rec-pick-why {
  margin-top: 8px;
  color: var(--fg-muted);
  font-size: 14px;
  line-height: 1.6;
}

.rec-none-h {
  font-family: var(--font-serif);
  font-size: clamp(1.25rem, 2.6vw, 1.55rem);
  margin-bottom: 10px;
}

.rec-none-p {
  color: var(--fg-muted);
  font-size: 14.5px;
  line-height: 1.65;
  max-width: 56ch;
}

/* .btn-tier is full width by default, which is wrong under a two-card
   recommendation. */
.rec-go {
  width: auto;
  display: inline-block;
  margin-top: 22px;
  padding-left: 28px;
  padding-right: 28px;
}

@media (max-width: 768px) {
  .rec { padding: 24px 20px; margin-top: 44px; }
  .rec-qs { gap: 20px; }
  .rec-go { display: block; width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  .rec-pick { animation: none; }
  .rec-opt span, .rec-go { transition: none; }
}

/* Hero guarantee line */
.hero-guarantee {
  margin-top: 26px;
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--fg-dim);
}

/* Product name above each service statement */

/* What's included, under pricing */
.stack {
  margin-top: 44px;
  padding: 30px 32px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-elev);
}

.stack-head h3 {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: clamp(1.3rem, 2.6vw, 1.7rem);
  margin-bottom: 8px;
}

.stack-head p {
  color: var(--fg-dim);
  font-size: 14px;
  line-height: 1.65;
  max-width: 62ch;
  margin-bottom: 24px;
}

.stack-list { display: grid; gap: 16px; }

.stack-list li {
  display: grid;
  gap: 4px;
  padding-left: 26px;
  position: relative;
}

.stack-list li::before {
  content: '✓';
  position: absolute;
  left: 0;
  top: 1px;
  color: var(--green);
  font-weight: 700;
}

.stack-list b { color: var(--fg); font-weight: 600; font-size: 15px; }
.stack-list span { color: var(--fg-muted); font-size: 14px; line-height: 1.6; }


@media (max-width: 768px) {
  .stack { padding: 22px 20px; margin-top: 32px; }
}

/* ═══════════════════════════════════════════════════════════════════
   ABOUT
   ═══════════════════════════════════════════════════════════════════ */
.about-body p {
  color: var(--fg-muted);
  line-height: 1.75;
  margin-bottom: 18px;
}

.about-body p b { color: var(--fg); font-weight: 600; }

.about-lead {
  font-family: var(--font-serif);
  font-size: 1.2rem;
  line-height: 1.6 !important;
  color: var(--fg) !important;
  margin-bottom: 24px !important;
}

/* Verifiable properties of the running system. Deliberately plain — the point
   is that each line can be checked, so anything that decorates it works
   against it. */
.about-proof-h {
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-dim);
  margin-bottom: 14px !important;
}

.about-proof {
  list-style: none;
  margin: 0 0 20px;
  padding: 0;
  border-left: 1px solid var(--border);
}

.about-proof li {
  position: relative;
  padding: 0 0 0 20px;
  margin-bottom: 12px;
  color: var(--fg-muted);
  font-size: 0.95rem;
  line-height: 1.65;
}

.about-proof li:last-child { margin-bottom: 0; }

.about-proof li::before {
  content: "";
  position: absolute;
  left: -3px;
  top: 0.62em;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--blue-light);
}

.about-facts {
  display: grid;
  /* auto-fit rather than a fixed 3, so the row stays even when a fact is
     added or removed instead of leaving a hole on the right */
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
  gap: 16px;
  margin: 32px 0 28px;
  padding: 22px 0;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}

.about-fact-label {
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--fg-dim);
  margin-bottom: 6px;
}

.about-fact-value {
  font-family: var(--font-serif);
  font-size: 1.05rem;
  color: var(--fg);
}

.about-contact p {
  font-size: 13px;
  color: var(--fg-dim);
  margin-bottom: 10px;
}

.about-contact a {
  display: block;
  color: var(--blue-light);
  font-size: 15px;
  margin-bottom: 6px;
  transition: color 0.2s;
}

.about-contact a:hover { color: var(--fg); }

@media (max-width: 768px) {
  .about-facts { grid-template-columns: 1fr; gap: 14px; }
  .about-lead { font-size: 1.1rem; }
}

/* Intro line under the "See it working" title */
.work-intro {
  max-width: 62ch;
  margin: -34px 0 46px;
  color: var(--fg-dim);
  font-size: 15px;
  line-height: 1.65;
}

.work-intro b {
  color: var(--fg);
  font-weight: 600;
}

@media (max-width: 768px) {
  .work-intro { margin: -20px 0 32px; font-size: 14px; }
}

/* ═══════════════════════════════════════════════════════════════════
   PROCESS — vertical timeline with self-drawing line
   ═══════════════════════════════════════════════════════════════════ */
.process-timeline {
  position: relative;
  max-width: var(--container-narrow);
  margin: 0 auto;
  padding-left: 64px;
}

/* Static dim line — full height background */
.process-line-bg {
  position: absolute;
  left: 19px;
  top: 6px;
  bottom: 6px;
  width: 1px;
  background: rgba(255, 255, 255, 0.08);
}

/* Active gold line — height controlled by JS based on scroll progress */
.process-line-fill {
  position: absolute;
  left: 19px;
  top: 6px;
  width: 1px;
  height: 0;
  background: linear-gradient(to bottom, var(--gold) 0%, rgba(212, 165, 116, 0.4) 100%);
  transition: height 0.7s cubic-bezier(0.25, 0.46, 0.45, 0.94);
  box-shadow: 0 0 8px rgba(212, 165, 116, 0.4);
}

.process-step {
  position: relative;
  display: grid;
  grid-template-columns: 1fr;
  padding-bottom: 48px;
}

.process-step:last-child { padding-bottom: 0; }

/* Marker dot on the timeline */
.process-marker {
  position: absolute;
  left: -52px;
  top: 8px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--bg);
  border: 1px solid rgba(255, 255, 255, 0.25);
  transition: border-color 0.45s ease, box-shadow 0.45s ease, transform 0.45s ease;
  z-index: 1;
}

.process-step.in-view .process-marker {
  border-color: var(--gold);
  background: var(--gold);
  box-shadow: 0 0 0 5px rgba(212, 165, 116, 0.12);
  transform: scale(1.05);
}

.process-text h3 {
  font-family: var(--font-serif);
  font-size: 1.4rem;
  font-weight: 500;
  margin-bottom: 8px;
  color: var(--fg);
}

.process-text p {
  font-size: 14.5px;
  color: var(--fg-muted);
  line-height: 1.65;
}

/* ═══════════════════════════════════════════════════════════════════
   FAQ
   ═══════════════════════════════════════════════════════════════════ */
.faq-list { display: flex; flex-direction: column; }

.faq-list details {
  border-bottom: 1px solid var(--border);
}

.faq-list summary {
  list-style: none;
  cursor: pointer;
  padding: 22px 0;
  display: flex;
  justify-content: flex-start;
  align-items: baseline;
  gap: 4px;
  font-size: 16px;
  font-weight: 500;
  color: var(--fg);
  transition: color 0.2s;
}

.faq-list summary::-webkit-details-marker { display: none; }
.faq-list summary::after {
  content: '+';
  font-size: 22px;
  font-weight: 300;
  color: var(--gold);
  flex-shrink: 0;
  width: 20px;
  text-align: center;
  transition: transform 0.2s;
}

.faq-list details[open] summary::after { content: '−'; }
.faq-list summary:hover { color: var(--gold); }

.faq-list details p {
  padding: 0 0 22px;
  margin: 0;
  color: var(--fg-muted);
  font-size: 14.5px;
  line-height: 1.7;
  max-width: 680px;
}

.faq-list details p b { color: var(--fg); font-weight: 600; }

/* An answer given as a list of points. */
.faq-list details .faq-points {
  list-style: none;
  margin: 0;
  padding: 0 0 22px;
  max-width: 680px;
  color: var(--fg-muted);
  font-size: 14.5px;
  line-height: 1.7;
}
.faq-points li { padding: 10px 0; border-top: 1px solid var(--border); }
.faq-points li:first-child { padding-top: 0; border-top: 0; }
.faq-points li b { color: var(--fg); font-weight: 600; }

/* Questions by system. Without script each group shows under its heading. With
   it, #faq gets .faq-tabbed: the headings go and the buttons choose the group. */
.faq-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 14px;
}
.faq-tabs[hidden] { display: none; }

.faq-tab {
  min-height: 44px;
  padding: 9px 12px;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.02);
  color: var(--fg-muted);
  font-family: var(--font-sans);
  font-size: 13px;
  line-height: 1.3;
  cursor: pointer;
  transition: border-color var(--t-2) var(--ease),
              background var(--t-2) var(--ease),
              color var(--t-2) var(--ease);
}
.faq-tab:hover { border-color: rgba(255, 255, 255, 0.28); color: var(--fg); }
.faq-tab.is-on {
  border-color: var(--blue);
  background: rgba(59, 130, 246, 0.14);
  color: var(--fg);
}

.faq-group + .faq-group { margin-top: 40px; }
.faq-group[hidden] { display: none; }
.faq-group-h {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: 1.3rem;
  color: var(--fg);
}
.faq-tabbed .faq-group + .faq-group { margin-top: 0; }
.faq-tabbed .faq-group-h { display: none; }

/* ═══════════════════════════════════════════════════════════════════
   START / CTA
   ═══════════════════════════════════════════════════════════════════ */
/* Constrain the content, not the element: .section no longer supplies
   margin:0 auto, so a max-width here left the whole cream panel pinned to the
   left edge instead of spanning the page. */
.section-cta {
  text-align: center;
  --pad-x: max(32px, calc((100% - var(--container-narrow)) / 2));
  padding-left: var(--pad-x);
  padding-right: var(--pad-x);
}

.section-cta .section-eyebrow { justify-content: center; }

.cta-sub {
  max-width: 520px;
  margin: -28px auto 36px;
  font-size: 1rem;
  color: var(--fg-muted);
  line-height: 1.65;
}

/* ── The enquiry form ── on the parchment panel, so its colours are the
   navy-on-cream of the system cards above it, not the dark theme's. */
.enq {
  max-width: 720px;
  margin: 0 auto;
  text-align: left;
}
.enq[hidden], .enq-done[hidden] { display: none; }

.enq-picked {
  min-height: 1.5em;          /* reserve the line so nothing jumps */
  margin-bottom: 18px;
  text-align: center;
  font-size: 13.5px;
  color: rgba(11, 18, 32, 0.6);
}

.enq-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px 18px;
}
.enq-wide { grid-column: 1 / -1; }

.enq-f { display: grid; gap: 6px; }
.enq-f > span {
  font-size: 12.5px;
  font-weight: 600;
  color: rgba(11, 18, 32, 0.78);
}
.enq-f > span i {
  margin-left: 6px;
  font-style: normal;
  font-weight: 400;
  color: rgba(11, 18, 32, 0.45);
}
.enq-f input,
.enq-f select,
.enq-f textarea {
  width: 100%;
  padding: 12px 14px;
  border: 1px solid rgba(11, 18, 32, 0.22);
  border-radius: 10px;
  background-color: rgba(255, 255, 255, 0.72);
  color: #0b1220;
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 1.4;
  transition: border-color var(--t-2) var(--ease), background-color var(--t-2) var(--ease);
}
/* One height for every single-line box. A browser sizes a dropdown its own
   way, so without this it sits a few pixels shorter than the inputs. */
.enq-f input,
.enq-f select { height: 47px; }
.enq-f select {
  -webkit-appearance: none;
  appearance: none;
  padding-right: 40px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%230b1220' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 15px center;
  cursor: pointer;
}
.enq-f select:invalid { color: rgba(11, 18, 32, 0.5); }      /* still on "Choose" */
.enq-f select option { color: #0b1220; }
.enq-f textarea { resize: vertical; min-height: 104px; }
.enq-f input:hover,
.enq-f select:hover,
.enq-f textarea:hover { border-color: rgba(11, 18, 32, 0.4); }
.enq-f input:focus,
.enq-f select:focus,
.enq-f textarea:focus {
  outline: 2px solid rgba(11, 18, 32, 0.28);
  outline-offset: 1px;
  border-color: #0b1220;
  background-color: #fff;
}
.enq-f [aria-invalid="true"] { border-color: #9b1c12; }

/* Off-screen rather than display:none, which some form-filling bots skip. */
.enq-trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* The button spans the fields; the message sits under it, the email line last. */
.enq-actions { margin-top: 22px; }
.enq-actions .btn-primary {
  display: block;
  width: 100%;
  text-align: center;
  cursor: pointer;
  font-family: var(--font-sans);
}
.enq-actions .btn-primary:disabled { opacity: 0.6; cursor: default; }
.enq-or { margin-top: 4px; text-align: center; font-size: 13.5px; color: rgba(11, 18, 32, 0.6); }
.enq-or a { color: #0b1220; text-decoration: underline; text-underline-offset: 3px; }

.enq-status {
  min-height: 1.5em;
  margin-top: 12px;
  text-align: center;
  font-size: 14px;
  color: rgba(11, 18, 32, 0.7);
}
.enq-status.is-bad { color: #9b1c12; font-weight: 500; }

.enq-done {
  max-width: 560px;
  margin: 8px auto 0;
  padding: 34px 28px;
  border: 1px solid rgba(11, 18, 32, 0.16);
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.5);
  text-align: center;
  color: #0b1220;
}
.enq-done:focus { outline: none; }
.enq-done h3 {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: clamp(1.4rem, 3vw, 1.8rem);
  margin-bottom: 8px;
}
.enq-done p { color: rgba(11, 18, 32, 0.65); font-size: 15px; }

@media (max-width: 640px) {
  .enq-grid { grid-template-columns: 1fr; }
  /* 16px stops iPhones zooming the page in when a field is tapped. */
  .enq-f input, .enq-f select, .enq-f textarea { font-size: 16px; }
}

/* ═══════════════════════════════════════════════════════════════════
   FOOTER
   ═══════════════════════════════════════════════════════════════════ */
.footer {
  background-color: var(--bg);
  border-top: 1px solid var(--border);
  padding: 48px 32px 36px;
  margin-top: 80px;
}

.footer-inner {
  max-width: var(--container);
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: 64px;
  align-items: start;
}

.footer-brand {
  max-width: 420px;
}

.footer-brand-link {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
}

.footer-shard {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
}

.footer-blurb {
  font-size: 14px;
  line-height: 1.65;
  color: var(--fg-muted);
}

.footer-contact h4 {
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 18px;
}

.footer-contact ul {
  list-style: none;
  padding: 0;
  margin: 0;
}

.footer-contact li {
  font-size: 14px;
  line-height: 2;
  color: var(--fg);
}

.footer-contact a {
  color: var(--fg);
  transition: color 0.2s;
}

.footer-contact a:hover { color: var(--gold); }

.footer-meta {
  grid-column: 1 / -1;
  margin-top: 32px;
  padding-top: 24px;
  border-top: 1px solid var(--border);
  font-size: 13px;
  color: var(--fg-dim);
  text-align: center;
}

@media (max-width: 700px) {
  .footer-inner { grid-template-columns: 1fr; gap: 36px; }
}

/* ═══════════════════════════════════════════════════════════════════
   SECTION AMBIENT BG — every dark section now takes the SAME colour as the
   body, which is what the hero shows. These used to carry five slightly
   different tints (warm, cool, neutral, slate, deep) and the first of them,
   #101116, was lighter than the body: that difference, plus a centre lift,
   was the visible seam under the hero. One colour end to end means the hero
   and the sections are literally the same tone and the only variation comes
   from the fixed dawn glow passing behind all of them.
   Still SOLID, so the hero's fixed starfield cannot show through the middle
   of a section; the gutters get their stars from the masked ::after instead.
   ═══════════════════════════════════════════════════════════════════ */
/* Opaque by default; see-through only where the column cover below exists to
   put the base colour back over the copy. */
.section-clients,
.section[data-num="01"],
.section[data-num="02"],
.section[data-num="03"],
.section[data-num="04"],
.section[data-num="05"] { background-color: var(--bg); }

@media (min-width: 1280px) {
  .section-clients,
  .section[data-num="01"],
  .section[data-num="02"],
  .section[data-num="03"],
  .section[data-num="04"],
  .section[data-num="05"] { background-color: transparent; }
}

/* ── Content column cover ───────────────────────────────────────────────
   The sections are transparent so the gutters ARE the hero's own layers,
   drifting with it rather than imitating it. This element puts the base colour
   back over the content column only, which is what makes the middle read as a
   lit panel against the starry sides, and keeps the field from running behind
   the copy. It carries the glow too, so the middle is not flatter than the
   gutters. Masked out well before the rail, so the frame still sits in the
   open. */
@media (min-width: 1280px) {
  .section-clients::after,
  .section[data-num="01"]::after,
  .section[data-num="02"]::after,
  .section[data-num="03"]::after,
  .section[data-num="04"]::after,
  .section[data-num="05"]::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    /* Fade begins 30px outside the column and finishes exactly AT it, so the
       copy is never over a partly transparent cover, while the rail at 28px
       out sits in the first 2px of the fade and stays effectively untouched. */
    --cover: calc((100% - var(--container)) / 2 - 30px);
    /* Flat base colour, and deliberately NO glow.
       The glow used to be painted here with background-attachment: fixed, to
       match the one on body::before. It does not survive: this element is
       masked, and a mask defeats fixed attachment, so the gradient silently
       re-anchored to the SECTION. Its centre sits at "110% of the element",
       which dropped a bright bloom at the foot of every section, cut off hard
       at the boundary. That was the band.
       The gutters still show the real glow through the transparent section,
       from body::before, which is unmasked and genuinely viewport-fixed. */
    background-color: var(--bg);
    -webkit-mask-image:
      linear-gradient(to right, transparent 0, transparent var(--cover),
        #000 calc(var(--cover) + 30px), #000 calc(100% - var(--cover) - 30px),
        transparent calc(100% - var(--cover)), transparent 100%);
    mask-image:
      linear-gradient(to right, transparent 0, transparent var(--cover),
        #000 calc(var(--cover) + 30px), #000 calc(100% - var(--cover) - 30px),
        transparent calc(100% - var(--cover)), transparent 100%);
  }
}





/* ── Content column frame ───────────────────────────────────────────────
   The blue hairline down each side of the column. It is an element rather
   than a background gradient purely so the corners can be rounded: a
   gradient rail cannot curve. Left and right borders only through 02 to 04,
   so the five sections read as one continuous frame; 01 caps it with a
   rounded top and 05 with a rounded bottom, which is what softens the
   entry out of the hero instead of starting on a hard square line.
   z-index -1 keeps it above the section background and below the copy. */
@media (min-width: 1280px) {
  .col-rail {
    position: absolute;
    /* Inset top and bottom so consecutive frames do not meet on a doubled
       line: the 22px each side leaves a 44px breath between one section's
       frame and the next, which is what separates them. */
    top: 22px;
    bottom: 22px;
    left: calc((100% - var(--container)) / 2 - 28px);
    right: calc((100% - var(--container)) / 2 - 28px);
    z-index: -1;
    pointer-events: none;
    border: 1px solid rgba(147, 197, 253, 0.26);
    border-radius: 30px;
  }
}

/* ═══════════════════════════════════════════════════════════════════
   SECTION 06 — INVERTED THEME ("press release" punctuation moment)
   Cream parchment bg, dark navy text, gold accent stays. Acts as a
   visual break from the dark sections — the climax of the page.
   ═══════════════════════════════════════════════════════════════════ */
.section[data-num="06"] {
  background-color: #f1ebde;
  margin-top: 1px;  /* hairline separator from previous dark section */
}

.section[data-num="06"] .section-eyebrow span:not(.dash) {
  color: rgba(11, 18, 32, 0.55);
}
.section[data-num="06"] .section-eyebrow .dash {
  background: #0b1220;
}

.section[data-num="06"] .section-title {
  color: #0b1220;
}

.section[data-num="06"] .cta-sub {
  color: rgba(11, 18, 32, 0.65);
}

/* Inverted buttons — primary is solid dark, ghost is dark outline */
.section[data-num="06"] .btn-primary {
  background: #0b1220;
  color: #f1ebde;
  border-color: #0b1220;
}
.section[data-num="06"] .btn-primary:hover {
  background: #1e293b;
  border-color: #1e293b;
}

.section[data-num="06"] .btn-ghost {
  background: transparent;
  color: #0b1220;
  border-color: rgba(11, 18, 32, 0.3);
}
.section[data-num="06"] .btn-ghost:hover {
  background: rgba(11, 18, 32, 0.05);
  border-color: rgba(11, 18, 32, 0.55);
}



/* ═══════════════════════════════════════════════════════════════════
   SECTION 06 — PRODUCT PICKER
   Five selectable cards on the parchment panel. Real <button>s, so they are
   keyboard reachable and announce their state through aria-pressed; the
   selected look is driven by .is-on rather than :focus, so tabbing through
   never looks like selecting.
   ═══════════════════════════════════════════════════════════════════ */
.pick-grid {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 14px;
  margin: 38px 0 34px;
  text-align: left;
}

.pick { grid-column: span 2; }
.pick:nth-child(4) { grid-column: 2 / span 2; }
.pick:nth-child(5) { grid-column: 4 / span 2; }

.pick {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 24px 18px 22px;
  border-radius: 16px;
  border: 1px solid rgba(11, 18, 32, 0.16);
  background: rgba(255, 255, 255, 0.5);
  color: #0b1220;
  font-family: inherit;
  cursor: pointer;
  transition: transform .18s cubic-bezier(.25,.46,.45,.94),
              border-color .18s ease, background .18s ease, box-shadow .18s ease;
}

.pick:hover {
  transform: translateY(-3px);
  border-color: rgba(11, 18, 32, 0.34);
  box-shadow: 0 10px 26px -18px rgba(11, 18, 32, 0.6);
}

.pick:focus-visible {
  outline: 2px solid #0b1220;
  outline-offset: 3px;
}

/* The tick sits in the card's corner, out of the flow, so it does not push
   the name and the line under it down the card. */
.pick-top {
  position: absolute;
  top: 12px;
  right: 12px;
  display: flex;
}

/* The empty circle is drawn from the start, so each card shows it can be
   ticked; selecting a card only changes its colours. */
.pick-tick {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  border: 1px solid rgba(11, 18, 32, 0.22);
  color: transparent;
  transition: background .18s ease, border-color .18s ease, color .18s ease;
}
.pick-tick svg { width: 11px; height: 11px; }

.pick-name {
  font-family: var(--font-serif);
  font-size: 1.12rem;
  line-height: 1.25;
  margin-top: 2px;
}

.pick-desc {
  font-size: 12px;
  line-height: 1.45;
  color: rgba(11, 18, 32, 0.55);
}

/* Selected: the card inverts to the same navy as the primary button, so the
   choice reads at a glance from across the section. */
.pick.is-on {
  background: #0b1220;
  border-color: #0b1220;
  color: #f1ebde;
  box-shadow: 0 14px 30px -18px rgba(11, 18, 32, 0.85);
}
.pick.is-on .pick-desc   { color: rgba(241, 235, 222, 0.6); }
.pick.is-on .pick-tick   { background: var(--gold); border-color: var(--gold); color: #0b1220; }


@media (max-width: 900px) {
  .pick-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pick, .pick:nth-child(4), .pick:nth-child(5) { grid-column: auto; }
  .pick:nth-child(5):last-child { grid-column: 1 / -1; }
}
@media (max-width: 520px) {
  .pick-grid { grid-template-columns: 1fr; gap: 10px; }
  .pick { padding: 18px 15px 16px; }
}

@media (prefers-reduced-motion: reduce) {
  .pick, .pick:hover, .pick:active { transition: none; transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════
   LINK UNDERLINE ANIMATION — nav + footer links
   ═══════════════════════════════════════════════════════════════════ */
.nav-links a,
.footer-contact a {
  position: relative;
  display: inline-block;
  padding: 2px 0;
}

.nav-links a::after,
.footer-contact a::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 1px;
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.32s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.nav-links a:hover::after,
.footer-contact a:hover::after {
  transform: scaleX(1);
}



/* ═══════════════════════════════════════════════════════════════════
   DAWN
   The business is called Dawnbreaker, the palette runs night-to-dawn, and the
   page contained a dawn glow it never used and an inversion at 06 it never
   explained. This connects them: as the cream panel approaches, the night
   drains out of the whole page. Stars fade, comets go out, the blue floor glow
   warms and swells into gold, and the nav bleaches from dark glass toward
   parchment — so 06 does not cut in, it dawns.

   One scalar drives all of it. --dawn is written by the scroll handler that
   already runs, onto each layer below that reads it (never onto the root,
   which would restyle the whole page on every scroll frame). All of these are position:fixed
   already, so the ramps are compositor-only.
   ═══════════════════════════════════════════════════════════════════ */
.starfield { opacity: calc(1 - var(--dawn, 0)); }
.page-comets { opacity: calc(1 - var(--dawn, 0)); }

.dawn-veil {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: var(--dawn, 0);
  background:
    radial-gradient(ellipse 120% 62% at 50% 118%,
      rgba(241, 235, 222, 0.92) 0%,
      rgba(212, 165, 116, 0.55) 34%,
      rgba(30, 58, 138, 0.10) 62%,
      transparent 78%);
}

/* The nav does NOT bleach with --dawn. It used to, on the theory that the one
   piece of chrome crossing the boundary should cross it too -- but the bar's
   text and logo are light, so as the cream came up underneath them the whole
   header faded to near-invisible over section 06. It keeps its night background
   from the base rule and stays readable over both halves of the page. */

/* ═══════════════════════════════════════════════════════════════════
   DEMO WAKE
   A gold line sweeps each browser frame as it enters view and the software
   comes alive behind it: desaturated and dim ahead of the line, fully lit
   behind. The sweep is causal, not decorative — its trailing edge is what
   starts the demo running, so the page's beam is visibly the thing switching
   each system on.
   ═══════════════════════════════════════════════════════════════════ */
.browser-body { position: relative; }

.scan {
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(90deg,
    transparent 0%,
    rgba(212, 165, 116, 0) calc(var(--scan, 0) * 100% - 160px),
    rgba(212, 165, 116, 0.55) calc(var(--scan, 0) * 100%),
    rgba(212, 165, 116, 0) calc(var(--scan, 0) * 100% + 2px),
    transparent 100%);
}

.prod-demo .work-frame {
  filter: saturate(0.25) brightness(0.62);
  transition: filter 0.9s var(--ease-out, ease);
}
.prod-demo.is-awake .work-frame { filter: none; }
.prod-demo.is-scanning .scan { opacity: 1; animation: scanSweep 0.9s cubic-bezier(0.22, 0.61, 0.36, 1) forwards; }

/* Fades out over the last fifth: the animation holds its end state, and the
   band behind the line would otherwise stay on the frame's right edge. */
@keyframes scanSweep {
  from { --scan: 0; opacity: 1; }
  80%  { opacity: 1; }
  to   { --scan: 1; opacity: 0; }
}
@property --scan {
  syntax: '<number>';
  inherits: false;
  initial-value: 0;
}

@media (prefers-reduced-motion: reduce) {
  /* No sweep, but the demos must still end up lit rather than stuck grey. */
  .prod-demo .work-frame { filter: none; transition: none; }
  .prod-demo.is-scanning .scan { animation: none; opacity: 0; }
  .dawn-veil { transition: none; }
}


/* ═══════════════════════════════════════════════════════════════════
   PRESS STATES
   Nothing on the page acknowledged a tap. That matters most on the WhatsApp
   and email calls to action: handing off to another app takes one to three
   seconds, and a control that stays inert for that long reads as broken and
   gets tapped again. It matters more here than on most sites because
   -webkit-tap-highlight-color is transparent further up, which removed the
   browser's own grey flash and left nothing in its place.
   Pressed in fast and released on the normal hover duration, so it feels like
   a button bottoming out rather than an animation.
   ═══════════════════════════════════════════════════════════════════ */
.btn-primary:active,
.btn-ghost:active,
.btn-tier:active,
.btn-pill:active,
.prod-index-nav a:active {
  transform: translateY(1px) scale(0.985);
  transition-duration: var(--t-1);
}
.btn-primary:active { background: var(--blue-deep); border-color: var(--blue-deep); }
.btn-ghost:active,
.btn-tier:active,
.btn-pill:active { background: rgba(255, 255, 255, 0.07); border-color: var(--border-strong); }

/* The picker cards are large surfaces, so they dip rather than shrink. */
.pick:active { transform: translateY(1px) scale(0.995); transition-duration: var(--t-1); }
.section[data-num="06"] .btn-primary:active { background: #1e293b; border-color: #1e293b; }
.section[data-num="06"] .btn-ghost:active { background: rgba(11, 18, 32, 0.1); }

/* ═══════════════════════════════════════════════════════════════════
   SCROLL REVEAL — sections fade up as they enter viewport
   ═══════════════════════════════════════════════════════════════════ */
/* The reveal is applied to the section's CONTENT, never to the section itself.
   Fading the section faded its background cover too, so the content column went
   briefly see-through and the starfield flashed behind the copy on the way in:
   that was the visible seam between one section and the next.
   The transform mattered even more. A transformed element becomes the
   containing block for any position:fixed descendant, and .in-view left
   translateY(0) in place permanently, so the viewport-pinned comet layer was
   quietly being anchored to the section instead of the screen. Excluding the
   decorative layers here fixes the seam and restores the comets at once. */
/* .section-title is excluded: it has its own mask-rise above. The rest fade
   up, and lag the heading slightly so it leads its own body copy. */
.reveal-on-scroll > *:not(.col-rail):not(.section-title) {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity var(--t-4) ease, transform var(--t-4) var(--ease);
  transition-delay: 0.12s;
}

.reveal-on-scroll.in-view > *:not(.col-rail):not(.section-title) {
  opacity: 1;
  transform: translateY(0);
}

/* Set while a same-page anchor jump is in flight: the destination must be
   fully composed on arrival, not caught mid-entrance. */
.jump-settle .reveal-on-scroll > *:not(.col-rail):not(.section-title),
.jump-settle .reveal-on-scroll .section-title .line-inner {
  transition: none !important;
  opacity: 1 !important;
  transform: none !important;
}

/* Respect users who don't want motion */
@media (prefers-reduced-motion: reduce) {
  /* Meteors stay as static streaks; the scroll-driven travel is dropped. */
  .meteor { animation: none; translate: none; transform: none; opacity: var(--op); }
  /* The starfield stops sliding with scroll, and anchor jumps stop animating -
     scroll-linked motion is exactly what this preference asks to avoid. */
  .starfield-tile { transform: none !important; }
  .section[data-num]::after { background-position: 0 0; }
  html { scroll-behavior: auto; }

  /* Only animation and transition are cancelled here. `transform` used to be
     in this list, and it does not belong: .hero-shard and .hero-constellation
     .spec carry translate(-50%,-50%), which is their CENTRING, not decoration
     — clearing it threw the shard off the hero and collapsed the constellation
     out of its circle. */
  .anim .hero-headline .line-inner,
  .anim .fade-up,
  .reveal-on-scroll,
  .reveal-on-scroll > *,
  .hero-shard,
  .hero-shard polygon,
  .hero-constellation .spec,
  .hero-lines line,
  .process-line-fill,
  .section-title {
    animation: none !important;
    transition: none !important;
  }

  /* Anything parked out of view for an entrance must be put back by hand now
     that the blanket `transform: none` is gone. */
  .anim .hero-headline .line-inner { transform: translateY(0) !important; }
  .anim .fade-up,
  .reveal-on-scroll,
  .reveal-on-scroll > * { opacity: 1 !important; transform: none !important; }
  .section-title .line-inner { transform: translateY(0) !important; }

  /* The grain overlay only needs its drift stopped. It must keep its own low
     opacity: it is a fixed, full-viewport layer, so forcing it opaque would
     bury the entire page behind noise. */
  html::after {
    animation: none !important;
  }
  .hero-shard { opacity: 0.08 !important; }
  .hero-constellation .spec { opacity: 0.6 !important; }
  .hero-lines line { stroke-dashoffset: 0 !important; }
  .reveal-on-scroll { opacity: 1 !important; transform: none !important; }
}

/* ═══════════════════════════════════════════════════════════════════
   RESPONSIVE
   ═══════════════════════════════════════════════════════════════════ */
@media (max-width: 768px) {
  /* One row on phones: the wordmark and a Menu button. The links and
     "Start now" used to share the bar, which wrapped it to two rows (about
     131px) with the button pressed against the wordmark. They now open as a
     full-screen sheet under the bar; script.js toggles .is-open. */
  .nav {
    grid-template-columns: 1fr auto;
    padding: 12px 20px;
  }
  .nav-links,
  .nav > .btn-pill { display: none; }
  .brand-text { font-size: 13px; }
  .nav-menu {
    display: inline-flex;
    align-items: center;
    justify-self: end;
    min-height: 44px;
    padding: 0 0 0 16px;
    background: none;
    border: 0;
    color: var(--fg);
    font: inherit;
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    cursor: pointer;
  }

  /* backdrop-filter makes the bar the containing block for fixed children,
     which would trap the sheet inside it, so the open bar trades its blur for
     a solid ground. */
  .nav.is-open {
    background: var(--bg);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .nav.is-open .nav-links {
    display: flex;
    /* The desktop grid centres this list; a fixed box still honours
       justify-self, so it has to stretch or it shrinks to its text. */
    justify-self: stretch;
    flex-direction: column;
    position: fixed;
    top: var(--nav-h, 68px);
    left: 0;
    right: 0;
    bottom: 0;
    gap: 0;
    padding: 8px 24px 32px;
    background: var(--bg);
    overflow-y: auto;
  }
  .nav.is-open .nav-links li { border-bottom: 1px solid var(--border); }
  .nav.is-open .nav-links a {
    display: block;
    padding: 14px 0;
    font-family: var(--font-serif);
    font-size: 1.25rem;
    color: var(--fg);
  }
  .nav.is-open .nav-links a::after { display: none; }
  /* "Start now" sits straight under the links, at an ordinary button size,
     rather than pinned to the bottom of the screen. */
  .nav.is-open .nav-links .nav-sheet-cta { display: block; border-bottom: 0; padding-top: 24px; }
  .nav.is-open .nav-links .nav-sheet-cta a {
    padding: 11px 20px;
    border: 1px solid var(--border-strong);
    border-radius: 999px;
    font-family: var(--font-sans);
    font-size: 14px;
    font-weight: 500;
    text-align: center;
  }
  /* Locks the page on body, not html: body has overflow-x: hidden, and once
     html is not visible that turns body into its own scroll box, so the sticky
     bar stuck to body and jumped to the top of the page, off screen, leaving
     the page showing above the sheet. Locked on body, the value goes to the
     window as the lightbox's does, and the bar stays put. */
  html.nav-open body { overflow: hidden; }

  .hero { padding: 80px 24px 70px; }
  .hero-headline { font-size: 2.4rem; }
  .hero-sub { font-size: 0.95rem; }
  /* One sentence on a phone: stacked, the first half wraps and leaves
     "attendance," alone on a line. */
  .hero-sub span { display: inline; margin: 0; }

  .section { --pad-x: 24px; padding: 70px var(--pad-x); }
  .section-title { margin-bottom: 36px; }

  .process-timeline { padding-left: 48px; }
  .process-line-bg, .process-line-fill { left: 15px; }
  .process-marker { left: -38px; }
  .process-step { padding-bottom: 36px; }
  .section { --num-top: 24px; }

}

/* ═══════════════════════════════════════════════════════════════════
   PRODUCT JUMP INDEX
   ═══════════════════════════════════════════════════════════════════ */
.prod-index-nav {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 12px;
  margin: 0 0 72px;
}

.prod-index-nav a {
  display: block;
  padding: 20px 22px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-elev);
  transition: border-color 0.18s, background 0.18s, transform 0.18s;
}

.prod-index-nav a:hover {
  border-color: var(--border-strong);
  background: rgba(255, 255, 255, 0.035);
  transform: translateY(-2px);
}

.pin-t {
  display: block;
  font-family: var(--font-serif);
  font-size: 1.22rem;
  line-height: 1.2;
  color: var(--fg);
  letter-spacing: -0.01em;
  margin-bottom: 6px;
}

.pin-s {
  display: block;
  font-size: 10.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-dim);
}

/* Five entries. Three across between the full row and the phone layout, so
   the fifth never sits alone under a row of four. */
@media (max-width: 1130px) {
  .prod-index-nav { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 860px) {
  .prod-index-nav { grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 48px; }
  .prod-index-nav a { padding: 16px 16px; }
  .prod-index-nav a:nth-child(5):last-child { grid-column: 1 / -1; }
  .pin-t { font-size: 1.05rem; }
  .prod-points { columns: 1; }
}

/* ═══════════════════════════════════════════════════════════════════
   ABOUT — COMMITMENTS + TESTIMONIALS
   ═══════════════════════════════════════════════════════════════════ */
.pledge {
  margin-top: 56px;
  padding-top: 40px;
  border-top: 1px solid var(--border);
}

.pledge-h {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: clamp(1.4rem, 3vw, 1.9rem);
  letter-spacing: -0.01em;
  margin-bottom: 12px;
}

.pledge-sub {
  color: var(--fg-muted);
  font-size: 15px;
  line-height: 1.7;
  max-width: 66ch;
  margin-bottom: 30px;
}

.pledge-list { display: grid; gap: 18px; }

.pledge-list li {
  display: grid;
  gap: 4px;
  padding-left: 26px;
  position: relative;
}

.pledge-list li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 8px;
  width: 9px;
  height: 9px;
  border-radius: 2px;
  background: var(--green);
  opacity: 0.85;
}

.pledge-list b { color: var(--fg); font-weight: 600; font-size: 15.5px; }
.pledge-list span { color: var(--fg-muted); font-size: 14.5px; line-height: 1.65; }

@media (max-width: 768px) {
  .pledge { margin-top: 40px; padding-top: 30px; }
}

/* ═════════════════════════════════════════════════
   CLIENTS — the band between the hero and 01
   A printed page inside the dark site: every logo in one ink, turning to the
   client's own colours on hover, with what we built for them underneath.
   A fixed grid, never a scrolling marquee.
   ═════════════════════════════════════════════════ */
.section-clients { padding-top: 84px; padding-bottom: 64px; }

/* Totals row above the grid. */
.cw-stats {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  margin: 6px 0 34px;
  border-top: 1px solid var(--border-strong);
}
.cw-stat { padding: 22px 22px 0 0; }
.cw-stat + .cw-stat { padding-left: 22px; border-left: 1px solid var(--border); }
.cw-stat b {
  display: block;
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: clamp(2rem, 3.6vw, 2.8rem);
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--fg);
  font-variant-numeric: lining-nums tabular-nums;
}
.cw-stat span { display: block; margin-top: 10px; font-size: 13px; line-height: 1.5; color: var(--fg-muted); }

@media (max-width: 900px) {
  .cw-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 24px; }
  .cw-stat:nth-child(3) { padding-left: 0; border-left: 0; }
}

.cw {
  --paper: #f3efe6;
  --paper-2: #ebe5d8;
  --ink: #15181e;
  --ink-3: #7a7f8a;
  --rule: rgba(21, 24, 30, 0.12);
  background: var(--paper);
  color: var(--ink);
  border-radius: 18px;
  overflow: hidden;
  box-shadow: 0 40px 80px -40px rgba(0, 0, 0, 0.8);
}

/* The five clients in one row; "and more" is a slim line under them. */
.cw-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); }

.cw-cell {
  display: flex;
  flex-direction: column;
  min-height: 200px;
  padding: 22px 20px 20px;
  border-right: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  transition: background 0.35s ease;
}
.cw-cell:nth-child(5n) { border-right: 0; }

.cw-logo { flex: 1; display: grid; place-items: center; padding: 6px 4px 18px; }
.cw-logo > * { grid-area: 1 / 1; }
.cw-logo img {
  max-width: 76%;
  max-height: 88px;
  object-fit: contain;
  transition: opacity 0.45s ease, transform 0.45s ease;
}
.cw-logo img.mono { opacity: 0.82; }
.cw-logo img.color { opacity: 0; }
.cw-logo img.on-black { border-radius: 10px; max-height: 104px; }

@media (hover: hover) {
  .cw-cell:hover { background: #fbf9f4; }
  .cw-cell:hover .cw-logo img.mono { opacity: 0; }
  .cw-cell:hover .cw-logo img.color { opacity: 1; transform: scale(1.03); }
}

/* Touch screens have no hover to reveal the colours, so they show each
   client's own logo from the start. */
@media (hover: none) {
  .cw-logo img.mono { display: none; }
  .cw-logo img.color { opacity: 1; }
}

/* "and more" after the five: a slim line across the card on a wide
   screen, the sixth cell of the grid on a phone. */
.cw-more {
  grid-column: 1 / -1;
  min-height: 0;
  padding: 12px 22px;
  border: 0;
  flex-direction: row;
  justify-content: center;
  align-items: center;
}
.cw-more span {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 1.05rem;
  color: var(--ink-3);
}
@media (hover: hover) { .cw-cell.cw-more:hover { background: none; } }

.cw-meta { display: grid; gap: 4px; }
/* Room for the business line and a second system line, so names sit level
   across a row whether a client has one system or two. */
.cw-meta { min-height: 84px; align-content: start; text-align: center; }
.cw-name { font-size: 14px; font-weight: 600; letter-spacing: -0.005em; }
.cw-what { margin-bottom: 2px; font-size: 12.5px; line-height: 1.35; color: #4a4f5a; }
.cw-sys {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}

@media (max-width: 900px) {
  .cw-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cw-cell { min-height: 180px; padding: 20px 18px 18px; }
  .cw-cell:nth-child(5n) { border-right: 1px solid var(--rule); }
  .cw-cell:nth-child(2n) { border-right: 0; }
  .cw-cell:nth-last-child(-n+2) { border-bottom: 0; }
  .cw-more { grid-column: auto; flex-direction: column; justify-content: center; }
  .cw-more span { font-size: 1.3rem; }
}

@media (max-width: 768px) {
  .section-clients { padding-top: 64px; padding-bottom: 48px; }
}

@media (max-width: 560px) {
  .cw-cell { min-height: 170px; padding: 16px 14px; }
  .cw-sys { font-size: 9.5px; letter-spacing: 0.08em; }
}

@media (prefers-reduced-motion: reduce) {
  .cw-logo img, .cw-cell { transition: none; }
}

/* ═════════════════════════════════════════════════
   CUSTOM SYSTEM — the examples list beside the copy, then case studies
   of recent builds, each opening its screens in the #shots dialog.
   ═════════════════════════════════════════════════ */
.built {
  padding: 22px 24px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-elev);
}

.built-h {
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-muted);
  margin-bottom: 14px;
}

.built-list { display: grid; gap: 14px; }

.built-list li {
  padding-top: 14px;
  border-top: 1px solid var(--border);
}

.built-list li:first-child { padding-top: 0; border-top: none; }

.built-list b {
  display: block;
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: 1.12rem;
  color: var(--fg);
  margin-bottom: 4px;
}

.built-list span {
  font-size: 14px;
  line-height: 1.6;
  color: var(--fg-muted);
}

/* The examples panel beside the Custom System copy: name and one line, close together. */
.built-list li { display: grid; gap: 10px; }
.built-ex .built-list li { gap: 3px; }
.built-list b { margin-bottom: 0; }
.built-ex .built-h {
  font-family: var(--font-serif);
  font-size: 1.65rem;
  font-weight: 400;
  letter-spacing: -0.01em;
  line-height: 1.2;
  text-transform: none;
  color: var(--fg);
  margin-bottom: 20px;
}

/* ── Case studies ──
   One structure per case (head, stage, features, outcome); each case places
   those four areas differently and sets its own palette through the --c-*
   and window variables. The stage is a grid with every layer in one cell, so
   the windows and phones are placed with margins in % of the stage width and
   scale together. */
.cs-set {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 40px;
  margin-top: 76px;
}

.cs {
  --c-fg: var(--fg);
  --c-body: var(--fg-muted);
  --c-line: var(--border);
  --c-acc: var(--gold);
  --c-focus: var(--blue-light);
  --pad: 52px;
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "head" "stage" "feats" "out";
  align-items: start;
  min-width: 0;
  color: var(--c-body);
}

.cs-head { grid-area: head; min-width: 0; }
.cs-shot { grid-area: stage; }
.cs-feats { grid-area: feats; min-width: 0; }
.cs-out { grid-area: out; min-width: 0; }

.cs-booking,
.cs-finance,
.cs-trade {
  padding: var(--pad);
  border-radius: var(--radius);
  background: var(--c-bg);
}

.cs-meta {
  margin-bottom: 14px;
  font-size: 13px;
  font-weight: 500;
  line-height: 1.5;
  color: var(--c-acc);
}

.cs-t {
  font-family: var(--font-serif);
  font-size: clamp(1.6rem, 1.5vw + 0.75rem, 2.15rem);
  font-weight: 400;
  line-height: 1.08;
  letter-spacing: -0.02em;
  color: var(--c-fg);
  text-wrap: balance;
}

.cs-job {
  max-width: 48ch;
  margin-top: 14px;
  font-size: 16px;
  line-height: 1.65;
  color: var(--c-body);
}

.cs-feats li {
  padding: 12px 0;
  border-top: 1px solid var(--c-line);
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--c-body);
}

.cs-feats li:last-child { border-bottom: 1px solid var(--c-line); }

.cs-result {
  margin-top: 24px;
  font-family: var(--font-serif);
  font-size: 1.4rem;
  font-weight: 400;
  line-height: 1.28;
  letter-spacing: -0.01em;
  color: var(--c-fg);
  text-wrap: balance;
}

.cs-k {
  display: block;
  margin-bottom: 8px;
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: 0;
  color: var(--c-acc);
}

.cs-out p { font-size: 15px; line-height: 1.6; color: var(--c-body); }

.cs-ask {
  display: inline-block;
  margin-top: 8px;
  padding: 11px 0;
  font-size: 14.5px;
  font-weight: 600;
  line-height: 22px;
  color: var(--c-acc);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 5px;
}

.cs-end { white-space: nowrap; }

.cs-ask [aria-hidden] {
  display: inline-block;
  margin-left: 8px;
  transition: transform var(--t-2) var(--ease);
}

.cs a:focus-visible { outline-color: var(--c-focus); }

/* The stage: the whole composition is one button that opens the screens. */
.cs-shot {
  display: block;
  width: 100%;
  min-width: 0;
  margin: 28px 0;
  padding: 0;
  border: 0;
  border-radius: 12px;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: zoom-in;
}

.cs-shot:focus-visible { outline: 2px solid var(--c-focus); outline-offset: 8px; border-radius: 12px; }

.cs-stage { display: grid; }
.cs-stage > * { grid-area: 1 / 1; min-width: 0; align-self: start; }

.cs-win {
  --w-bg: #f5f5f3;
  --w-edge: rgba(255, 255, 255, 0.1);
  --w-shadow: rgba(0, 0, 0, 0.72);
  display: block;
  position: relative;
  z-index: 1;
  overflow: hidden;
  border-radius: 10px;
  background: var(--w-bg);
  box-shadow: 0 0 0 1px var(--w-edge), 0 36px 64px -36px var(--w-shadow);
  transition: transform var(--t-2) var(--ease);
}

/* "See the screens" sits on the main screen's top corner. */
.cs-open {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  border-radius: 999px;
  background: rgba(13, 16, 21, 0.86);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.18), 0 8px 18px -10px rgba(0, 0, 0, 0.7);
  color: #f8fafc;
  font-size: 11.5px;
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
}

.cs-open svg {
  display: block;
  width: 11px;
  height: 11px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform var(--t-2) var(--ease);
}

.cs-win img {
  display: block;
  width: 100%;
  height: auto;
  background: var(--w-bg);
}

.cs-phone {
  --p-shadow: rgba(0, 0, 0, 0.7);
  display: block;
  position: relative;
  z-index: 2;
  padding: clamp(3px, 0.42vw, 6px);
  border-radius: 15% / 7%;
  background: #0d0f13;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12), 0 30px 54px -26px var(--p-shadow);
  transition: transform var(--t-2) var(--ease);
}

.cs-phone img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 12.5% / 5.8%;
  background: #f4f4f2;
}

@media (hover: hover) {
  .cs-shot:hover .cs-win { transform: translateY(-3px); }
  .cs-shot:hover .cs-phone { transform: translateY(-6px); }
  .cs-shot:hover .cs-open svg { transform: scale(1.15); }
  .cs-ask:hover [aria-hidden] { transform: translateX(3px); }
}

/* Driver booking: slate, amber detail, dashboard with the customer's phone. */
.cs-booking {
  --c-bg: #19202a;
  --c-fg: #f1f5f9;
  --c-body: #b5c0cd;
  --c-line: rgba(255, 255, 255, 0.09);
  --c-acc: #eec264;
  --c-focus: #fcd34d;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.06);
}

.cs-booking .cs-win { --w-bg: #f6f5f0; }
.cs-booking .cs-w1 { width: 84%; margin-left: 16%; }
.cs-booking .cs-p1 { width: 29%; margin-top: 13%; }

/* Campaign site: the page's own ground, a stone plate behind two builds. */
.cs-campaign .cs-plate {
  display: block;
  align-self: stretch;
  margin: 8% 0 0 15%;
  border-radius: var(--radius);
  background: #e9e2d6;
}

.cs-campaign .cs-win {
  --w-bg: #f7f4ee;
  --w-edge: rgba(42, 37, 32, 0.12);
  --w-shadow: rgba(42, 30, 18, 0.5);
}

.cs-campaign .cs-w1 { width: 90%; }
.cs-campaign .cs-w2 { width: 55%; margin: 42% 0 6% 41%; z-index: 2; }

/* Finance viewer: deep green, the ledger runs off the panel's edges. */
.cs-finance {
  --c-bg: #0f2a22;
  --c-fg: #edf6f1;
  --c-body: #b4cac0;
  --c-line: rgba(255, 255, 255, 0.1);
  --c-acc: #67d6aa;
  --c-focus: #86efac;
  overflow: hidden;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.05);
}

.cs-finance .cs-win {
  --w-bg: #121418;
  --w-shadow: rgba(0, 0, 0, 0.8);
}

/* Sales and stock: restrained neutrals; colour comes from the screens. */
.cs-trade {
  --c-bg: #ebe5d8;
  --c-fg: #0f172a;
  --c-body: #3e4758;
  --c-line: rgba(15, 23, 42, 0.13);
  --c-acc: #24307c;
  --c-focus: #1e3a8a;
}

.cs-trade .cs-win {
  --w-bg: #f5f6f9;
  --w-edge: rgba(15, 23, 42, 0.12);
  --w-shadow: rgba(15, 23, 42, 0.42);
}

.cs-trade .cs-phone { --p-shadow: rgba(15, 23, 42, 0.5); }
.cs-trade .cs-w1 { width: 100%; }
.cs-trade .cs-p1 { width: 29%; margin: 35% 0 0 38%; }
.cs-trade .cs-p2 { width: 29%; margin: 43% 0 0 71%; }

/* ── Wide screens: the four systems in a two-by-two grid ── */
@media (min-width: 980px) {
  .cs-set {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 28px;
  }

  /* Title and description, then the screens, then the link. The link sits on
     the card's bottom edge so the two cards in a row end level. */
  .cs {
    --pad: 34px;
    grid-template-areas: "head" "stage" "out";
    grid-template-rows: auto auto 1fr;
  }
  .cs-shot { margin: 28px 0 0; }
  .cs-out { align-self: end; margin-top: 28px; }
  .cs-job { font-size: 15.5px; }

  /* The campaign card has no panel of its own; a hairline keeps the grid's edges. */
  .cs-campaign {
    padding: var(--pad);
    border-radius: var(--radius);
    box-shadow: inset 0 0 0 1px var(--border);
  }
}

@media (max-width: 979px) {
  .cs { --pad: 36px; }
  .cs-out { margin-top: 22px; }
  .cs-finance .cs-win { --w-shadow: rgba(0, 0, 0, 0.6); }
}

@media (max-width: 600px) {
  .cs-set { gap: 28px; margin-top: 60px; }
  .cs { --pad: 20px; }
  .cs-booking, .cs-finance, .cs-trade { border-radius: 12px; }
  .cs-t { font-size: 1.6rem; }
  .cs-job { font-size: 15.5px; }
  .cs-result { font-size: 1.3rem; }
  .cs-shot { margin: 24px 0; }
  .cs-win { border-radius: 7px; }
  .cs-open { top: 7px; right: 7px; padding: 5px 8px; font-size: 10.5px; }
  .cs-campaign .cs-w2 { margin-top: 41%; }

  /* The stage reaches to 8px from the panel's sides and the phone screens
     come forward at about half its width. */
  .cs-booking .cs-shot,
  .cs-trade .cs-shot { width: calc(100% + 2 * var(--pad) - 16px); margin-inline: calc(8px - var(--pad)); }
  .cs-booking .cs-shot:focus-visible,
  .cs-trade .cs-shot:focus-visible { outline-offset: 4px; }
  .cs-booking .cs-w1 { width: 100%; margin-left: 0; }
  .cs-booking .cs-p1 { justify-self: center; width: min(52%, 210px); margin: 26% 0 0; }
  .cs-trade .cs-phone { width: min(48%, 200px); margin-top: 34%; }
  .cs-trade .cs-p1 { justify-self: end; margin-right: calc(50% + 4px); margin-left: 0; }
  .cs-trade .cs-p2 { justify-self: start; margin-left: calc(50% + 4px); }
  /* The ledger runs off the panel's right edge, as it does on wide screens. */
  .cs-finance .cs-shot { position: relative; isolation: isolate; width: calc(100% + var(--pad) + 24px); margin-right: calc(-24px - var(--pad)); }
  .cs-finance .cs-w1 { border-radius: 7px 0 0 7px; }
  .cs-finance .cs-open { right: 31px; }
  /* Its focus ring follows the visible part of the ledger, up to the panel's edge. */
  .cs-finance .cs-shot:focus-visible { outline: none; }
  .cs-finance .cs-shot::after {
    content: '';
    position: absolute;
    inset: 0 24px 0 0;
    z-index: 2;
    border-radius: 7px 0 0 7px;
    pointer-events: none;
  }
  .cs-finance .cs-shot:focus-visible::after { box-shadow: inset 0 0 0 2px var(--c-focus); }
}

/* Screens settle in as they reach the viewport. script.js adds is-armed only
   when the page can animate, and is-in once the case is in view. */
@keyframes csRise {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: none; }
}

@keyframes csFade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.cs.is-armed:not(.is-in) .cs-win,
.cs.is-armed:not(.is-in) .cs-phone,
.cs.is-armed:not(.is-in) .cs-plate { opacity: 0; }

.cs.is-in .cs-win,
.cs.is-in .cs-phone { animation: csRise 0.8s var(--ease-out) backwards; }
.cs.is-in .cs-plate { animation: csFade 0.9s var(--ease-out) backwards; }
.cs.is-in .cs-w2,
.cs.is-in .cs-p1 { animation-delay: 0.12s; }
.cs.is-in .cs-p2 { animation-delay: 0.22s; }

@media (prefers-reduced-motion: reduce) {
  .cs .cs-win,
  .cs .cs-phone,
  .cs .cs-plate,
  .cs .cs-open svg,
  .cs .cs-ask [aria-hidden] { animation: none !important; opacity: 1 !important; transition: none; transform: none !important; }
}

/* Printed pages show every screen, including ones never scrolled to. */
@media print {
  .cs.is-armed .cs-win,
  .cs.is-armed .cs-phone,
  .cs.is-armed .cs-plate { opacity: 1 !important; animation: none !important; }
}

/* Lightbox. A native dialog: Escape and focus handling come free. */
/* position is restated because "body > * { position: relative }" near the top
   of this file outranks the browser's own fixed positioning for an open
   dialog. Without it the lightbox sat at the top of the document and opening
   it scrolled the page up to meet it. */
.shots {
  position: fixed;
  inset: 0;
  margin: auto;
  width: min(1240px, 92vw);
  max-height: 90vh;
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: 16px;
  background: var(--bg-elev);
  color: var(--fg);
  overflow: auto;
}

.shots::backdrop {
  background: rgba(8, 12, 18, 0.86);
  backdrop-filter: blur(6px);
}

body:has(.shots[open]) { overflow: hidden; }

.shots-close {
  position: sticky;
  top: 12px;
  float: right;
  margin: 12px 12px 0 0;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1px solid var(--border-strong);
  background: var(--bg);
  color: var(--fg);
  font-size: 20px;
  line-height: 1;
  z-index: 1;
}

.shots-close:hover { background: rgba(255, 255, 255, 0.06); }

.shots figure { margin: 0; padding: 18px 20px 6px; clear: none; }

.shots figure + figure { border-top: 1px solid var(--border); }

.shots img {
  display: block;
  max-width: 100%;
  max-height: 80vh;
  margin: 0 auto;
  border: 1px solid var(--border);
  border-radius: 8px;
}

.shots .phones-row {
  display: flex;
  justify-content: center;
  gap: 18px;
  flex-wrap: wrap;
}

.shots .phones-row img { max-height: 76vh; }

.shots figcaption {
  padding: 10px 4px 12px;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--fg-muted);
  text-align: center;
}

@media (max-width: 768px) {
  .shots { width: 96vw; }
  .shots figure { padding: 12px 12px 4px; }
}

/* The privacy notice under the enquiry form (navy on the cream panel). The footer:
   the company line sits under the blurb, and the right column (e-mail, then the two
   legal pages) hugs the right edge. */
.enq-privacy { margin-top: 10px; text-align: center; font-size: 13px; color: rgba(11, 18, 32, 0.6); }
.enq-privacy a { color: #0b1220; text-decoration: underline; text-underline-offset: 3px; }
.footer-copy { margin-top: 14px; font-size: 13px; color: var(--fg-dim); }
.footer-contact { justify-self: end; }
@media (max-width: 700px) {
  .footer-contact { justify-self: start; }
}

/* ── Recent builds, grouped by size ── */
.cs-tier { display: grid; gap: 22px; min-width: 0; }
.cs-tier + .cs-tier { margin-top: 36px; }
.cs-tier-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 18px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--border);
}
.cs-tier-t {
  font-family: var(--font-serif);
  font-size: clamp(1.9rem, 3.4vw, 2.6rem);
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--fg);
}
/* Size mark before each tier name: one star, three joined, then a constellation. */
.cs-size { display: inline-block; width: 0.95em; height: 0.95em; margin-right: 0.38em; vertical-align: -0.12em; overflow: visible; }
.cs-size .s { fill: var(--gold); }
.cs-size .l { fill: none; stroke: var(--gold); stroke-width: 1.3; opacity: 0.5; }
.cs-tier-row { display: grid; gap: 28px; min-width: 0; }

/* Small builds: the live demos, each on its own brand's ground. */
.cs-live .prod-demo { grid-area: stage; min-width: 0; margin: 28px 0 0; }
.cs-live .prod-demo-cap { color: var(--c-body); }
.cs-live .prod-demo-cap b { color: var(--c-fg); }
.cs-punch,
.cs-remind {
  padding: var(--pad);
  border-radius: var(--radius);
  background: var(--c-bg);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.06);
}
.cs-punch {
  --c-bg: #0d2b2a;
  --c-fg: #eef6f2;
  --c-body: #b9cdc6;
  --c-line: rgba(255, 255, 255, 0.1);
  --c-acc: #d2e17b;
  --c-focus: #d2e17b;
}
.cs-remind {
  --c-bg: #2a1828;
  --c-fg: #f7eaf2;
  --c-body: #cdb9c9;
  --c-line: rgba(255, 255, 255, 0.1);
  --c-acc: #eac46a;
  --c-focus: #eac46a;
}

@media (min-width: 980px) {
  .cs-tier { grid-column: 1 / -1; }
  .cs-tier-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 600px) {
  .cs-punch, .cs-remind { border-radius: 12px; }
}

/* ── Process: the testing step decides go-live ── */
.process-step.is-key .process-marker {
  border-color: var(--gold);
  background: var(--gold);
  box-shadow: 0 0 0 6px rgba(212, 165, 116, 0.16);
}
.process-checks { display: grid; gap: 8px; margin-top: 16px; }
.process-checks li {
  position: relative;
  padding-left: 24px;
  font-size: 15px;
  line-height: 1.5;
  color: var(--fg);
}
.process-checks li::before {
  content: '';
  position: absolute;
  left: 2px;
  top: 0.42em;
  width: 10px;
  height: 5px;
  border-left: 2px solid var(--gold);
  border-bottom: 2px solid var(--gold);
  transform: rotate(-45deg);
}
