/* The three custom properties the sheet reads. next/font generates these
   on the Next build; off-Next they are declared against the Google families. */
.cs-fontvars, .cs {
  --cs-display: "Bricolage Grotesque";
  --cs-body: "Hanken Grotesk";
  --cs-mono: "IBM Plex Mono";
}
/* The page is the whole document here rather than one route in an app, so
   the ink ground has to reach the edges — otherwise a short viewport shows
   white under the footer. */
html, body { margin: 0; padding: 0; background: #0E1C16; }

/* ── "Back to site" ──
   Only exists on the agency-hosted copies. On csignal.io the nav genuinely
   leaves for another domain and rightly carries ArrowUpRight; here the same
   link is a step back up the site it is already on, so the arrow leads the
   label instead of trailing it. The icon rules upstream assume a TRAILING
   external mark, hence the overrides — done here rather than in deck.tsx,
   because the Next page still wants the old behaviour. */
.cs-nav__back { display: inline-flex; align-items: center; gap: 7px; }
.cs-nav__links a.cs-nav__back svg,
.cs-menu__in > a.cs-nav__back svg { margin: 0; flex: none; opacity: .55; }
.cs-nav__links a.cs-nav__back svg { width: 15px; height: 15px; }
.cs-menu__in > a.cs-nav__back svg { width: 20px; height: 20px; }


/* ─────────────────────────────────────────────────────────────────────────
   NO BACKTICKS ANYWHERE IN THIS STRING, including inside comments. It is a
   template literal, so one backtick ends it and the error surfaces as a
   nonsense TypeScript parse failure hundreds of lines away. Quote CSS in
   prose with plain text, never with code ticks.
   ───────────────────────────────────────────────────────────────────────── */
.cs { --cs-max: 1600px; --ink:#0E1C16; --lime:#D3F502; --paper:#F3F5EE; --blue:#2F6BFF; --coral:#FF5A2C;
  background: var(--ink); font-family: var(--cs-body), system-ui, sans-serif; -webkit-font-smoothing: antialiased; }
.cs *, .cs *::before, .cs *::after { box-sizing: border-box; }
/* Margin reset, wrapped in :where() so it carries ZERO specificity.
   Written as ".cs p" it scored 0,1,1 and quietly beat every single-class rule
   below it — .cs-lede, .cs-reco__h, .cs-card__b, .cs-step__h and the rest all
   had their margins silently dropped, which is why spacing edits appeared to
   do nothing. :where() makes the reset losable, which is the whole point of a
   reset. */
.cs :where(p, h1, h2, h3, blockquote) { margin: 0; }
.cs a { text-decoration: none; }

.cs-btn { display: inline-block; font-weight: 700; font-size: 15px; padding: 13px 24px; border-radius: 999px; transition: background .12s ease-out, border-color .12s ease-out, color .12s ease-out; }
.cs-btn--lime { background: var(--lime); color: var(--ink); }
.cs-btn--lime:hover { background: #E2FF3A; }
.cs-btn--outline { border: 1px solid rgba(255,255,255,0.25); color: #fff; }
.cs-btn--outline:hover { border-color: var(--lime); color: var(--lime); }
.cs-btn--ghost { background: #F1F3EC; color: #55665C; }
.cs-btn--ink { background: var(--ink); color: var(--paper); }
.cs-btn--ink:hover { background: #1B3226; }
.cs-btn__ext { display: inline-block; width: 15px; height: 15px; margin-left: 6px; vertical-align: -2px; }
/* The large pair is the hero's only real furniture, so it scales with the
   viewport rather than staying at its desktop weight. At 17px/19px/34px the
   two buttons took most of a phone screen and read as the loudest thing on
   the fold, above the headline they are meant to follow.

   The floors are lower than a type scale would suggest on purpose. These are
   the widest elements in the fold — two pills side by side have to share a
   330px row — so their padding is a layout constraint before it is a size
   choice, and the ceilings are untouched, so the desktop pair is exactly as
   it was. */
.cs-btn--lg { font-size: clamp(14px, 1.4vw, 17px); padding: clamp(12px, 1.5vw, 19px) clamp(19px, 2.7vw, 34px); }
.cs-btn--sm { font-size: 13px; padding: 11px 19px; }

/* ── Nav ─────────────────────────────────────────────────────────────── */
/* WIDTH CAP. Nothing was bounded, so on a wide monitor the headline and its
   note were pushed apart indefinitely and the cards kept stretching. Each
   section's children are capped and centred rather than the section itself,
   so backgrounds and bleeding shapes still run edge to edge.

   The hero is capped too, but its shape layer had to be capped WITH it or the
   shapes would have stayed pinned to the viewport while the deck moved inward.
   See .cs-hero__deco. */
/* ── NOTHING IN THE BAR MAY WRAP ─────────────────────────────────────────
   Every item was allowed to break internally, so the row degraded by
   wrapping words rather than by getting tighter: "What it / does", "How it /
   works", "Log / in", and the lime CTA collapsing into a circle around "Work
   / with / Charle". It started at about 1320px, which is a common laptop, and
   the hide-the-links breakpoint at 1080 was far too late to catch it.

   The fix is in three parts and the order matters:
     1. nowrap on every item, so a word can never be the thing that gives.
     2. logo and actions are flex:none, links are the only shrinkable track —
        otherwise the browser squeezes the wordmark and the button instead.
     3. the links gap and type size are clamped, so the row tightens
        CONTINUOUSLY between 1080 and 1400 instead of at one hard step.

   Padding stays at a fixed 60px on purpose. The hero and every section below
   use the same figure, and clamping it here would slide the wordmark out of
   line with the h1 at every width in between. */
.cs-nav { position: relative; z-index: 10; display: flex; align-items: center; justify-content: space-between; gap: clamp(18px, 2vw, 30px); padding: 26px 60px; max-width: calc(var(--cs-max) + 120px); margin-inline: auto; }
/* Above the overlay, so the bar still reads normally with the menu open. */
.cs-nav__logo { position: relative; z-index: 30; display: flex; align-items: center; gap: 14px; flex: none; }
.cs-nav__logo svg { display: block; }
.cs-nav__rule { width: 1px; height: 22px; background: rgba(255,255,255,0.2); }
.cs-nav__by { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 500; color: #fff; opacity: 0.5; white-space: nowrap; }
.cs-nav__by svg { display: block; }
.cs-nav__links { display: flex; align-items: center; gap: clamp(16px, 2.1vw, 34px); min-width: 0; }
.cs-nav__ext { display: inline-block; width: 13px; height: 13px; margin-left: 3px; vertical-align: -1px; opacity: .55; }
.cs-nav__links a { color: rgba(255,255,255,0.72); font-size: clamp(13.5px, 1.1vw, 15px); font-weight: 600; white-space: nowrap; }
.cs-nav__links a:hover { color: var(--lime); }
.cs-nav__acts { display: flex; align-items: center; gap: 14px; flex: none; }
.cs-nav__login { color: #fff; font-size: 15px; font-weight: 700; padding: 12px 18px; white-space: nowrap; }
.cs-nav__login:hover { color: var(--lime); }
.cs-nav__acts .cs-btn { white-space: nowrap; }

/* ── Mobile menu ─────────────────────────────────────────────────────────
   Below 1080 the links were simply switched off and nothing replaced them,
   so the whole page was unreachable on a phone. The burst and the panel are
   the same markup at every width; only the media queries below decide
   whether they are on screen.

   The overlay is position:fixed but lives INSIDE .cs-nav, which is the only
   reason the burger can sit on top of it. .cs-nav carries z-index:10 and so
   opens a stacking context; a z-index on the button is measured against its
   siblings inside that context, not against the page, so 30 over the
   overlay's 20 works and the pair still clears the hero together. */
.cs-burger { display: none; position: relative; z-index: 30; flex: none; align-items: center; justify-content: center; width: 44px; height: 44px; margin-right: -10px; padding: 0; border: 0; border-radius: 12px; background: none; color: #fff; cursor: pointer; }
.cs-burger:hover { color: var(--lime); }
.cs-burger:focus-visible { outline: 2px solid var(--lime); outline-offset: 2px; }
.cs-burger svg { width: 26px; height: 26px; }
/* ── THE CURTAIN ─────────────────────────────────────────────────────────
   It comes down from behind the bar rather than fading, because a fade
   between two near-identical ink surfaces reads as a flicker rather than as
   a panel arriving. Translating the whole box also means the closed state is
   genuinely off screen, so nothing here needs pointer-events juggling.

   The exit is the same transition run backwards, which is only possible
   because the component keeps the panel mounted for the duration — see the
   note in mobile-menu.tsx. Anything the enter state adds, including the
   stagger delays below, has to live in the .is-open rules so that closing
   drops them and the whole thing leaves at once. */
.cs-menu { position: fixed; inset: 0; z-index: 20; background: var(--ink); overflow-y: auto; overscroll-behavior: contain; padding: 104px 22px 40px;
  transform: translateY(-100%);
  transition: transform .42s cubic-bezier(.16, 1, .3, 1); }
.cs-menu.is-open { transform: none; }
/* The hidden attribute has to beat the display above, or the panel is only
   visually gone and its links stay in the tab order. */
.cs-menu[hidden] { display: none; }
/* The contents follow the curtain in rather than riding it down, which is
   what stops the panel reading as one flat sheet of type. Delay only, no
   separate duration per item — six steps of 40ms is a cascade, more is a
   queue you wait through. */
.cs-menu__in > a, .cs-menu__acts { opacity: 0; transform: translateY(9px);
  transition: opacity .3s ease, transform .4s cubic-bezier(.16, 1, .3, 1); }
.cs-menu.is-open .cs-menu__in > a, .cs-menu.is-open .cs-menu__acts { opacity: 1; transform: none; }
.cs-menu.is-open .cs-menu__in > a:nth-child(1) { transition-delay: .12s; }
.cs-menu.is-open .cs-menu__in > a:nth-child(2) { transition-delay: .16s; }
.cs-menu.is-open .cs-menu__in > a:nth-child(3) { transition-delay: .20s; }
.cs-menu.is-open .cs-menu__in > a:nth-child(4) { transition-delay: .24s; }
.cs-menu.is-open .cs-menu__in > a:nth-child(5) { transition-delay: .28s; }
.cs-menu.is-open .cs-menu__in > a:nth-child(6) { transition-delay: .32s; }
.cs-menu.is-open .cs-menu__acts { transition-delay: .36s; }
/* Reduced motion gets the panel, not the performance. It still has to
   APPEAR — display:none would take the menu away entirely — so only the
   movement goes. */
@media (prefers-reduced-motion: reduce) {
  .cs-menu, .cs-menu__in > a, .cs-menu__acts { transition: none; }
  .cs-menu__in > a, .cs-menu__acts { opacity: 1; transform: none; }
}
.cs-menu__in { display: flex; flex-direction: column; gap: 4px; max-width: 420px; }
/* ── THE CHILD COMBINATOR IS LOAD-BEARING, NOT TIDINESS ──────────────────
   Written as a descendant selector this also caught the two anchors inside
   .cs-menu__acts, and it beat every button rule it touched: 0,1,1 against
   the 0,1,0 of .cs-btn--lime. The CTA came out with 27px display type,
   padding 13px 0 — no horizontal padding at all, so the label sat flush
   against the left edge — a hairline under it, and white text on lime.

   The flex was the other half. Text inside a flex container is an anonymous
   flex item, and text-align does not place flex items, so the centring on
   .cs-menu__acts .cs-btn was silently doing nothing.

   The links are direct children of .cs-menu__in and the actions are one
   level deeper, so a single > separates them cleanly. */
.cs-menu__in > a { display: flex; align-items: center; font-family: var(--cs-display), sans-serif; font-weight: 700; font-size: 27px; letter-spacing: -.02em; color: #fff; padding: 13px 0; border-bottom: 1px solid rgba(255,255,255,0.08); }
.cs-menu__in > a:active { color: var(--lime); }
.cs-menu__in > a svg { width: 17px; height: 17px; margin-left: 7px; opacity: .55; }
.cs-menu__acts { display: flex; flex-direction: column; align-items: stretch; gap: 12px; margin-top: 34px; }
/* Full-width and centred. display:block matters — .cs-btn is inline-block,
   and text-align only centres the label once the box is a block. */
.cs-menu__acts .cs-btn, .cs-menu__login { display: block; text-align: center; font-size: 16px; padding: 16px 24px; }
.cs-menu__login { color: #fff; font-weight: 700; border: 1px solid rgba(255,255,255,0.25); border-radius: 999px; }

/* ── Hero ────────────────────────────────────────────────────────────── */
.cs-hero { position: relative; padding: 66px 60px 90px; overflow-x: clip; }
/* Shape layer. Clipped to the hero so anything bleeding off the right stops at
   the viewport edge, and inert so it can never eat a click on the CTAs. */
/* Capped and centred to match .cs-hero__in, so every shape offset measured
   from the right edge still tracks the deck once the content stops filling the
   window. The extra 120px keeps its right edge 60px outside the content edge,
   exactly where it sat when the layer was full-bleed — otherwise every tuned
   offset would jump inward by 60px.

   overflow-x visible with overflow-y clip: shapes still bleed sideways past
   the cap and are clipped by the hero's own overflow-x, while anything falling
   below the fold is cut. The pair is legal precisely because it is clip
   rather than hidden; hidden would force the other axis to auto. */
.cs-hero__deco {
  position: absolute; inset: 0;
  max-width: calc(var(--cs-max) + 120px);
  margin-inline: auto;
  overflow-x: visible;
  overflow-y: clip;
  pointer-events: none;
  z-index: 0;
}

/* TWO ELEMENTS PER SHAPE, deliberately. The outer one carries the pointer
   parallax and the inner one carries the idle float, because a CSS animation
   and a JS-written transform cannot share an element — the animation wins and
   the parallax silently does nothing.

   --mx / --my are written once on the layer by hero-deco.tsx in the range
   -0.5 to 0.5. Each shape scales them by its own --depth, so one style write
   moves everything by a different amount. */
.cs-deco { position: absolute; display: block; will-change: transform;
  transform: translate3d(calc(var(--mx, 0) * var(--depth, 0) * 1px), calc(var(--my, 0) * var(--depth, 0) * 1px), 0); }
.cs-deco__in { display: block; animation: cs-float 8s ease-in-out infinite; }
/* WHERE EACH SHAPE SITS. Measured from the RIGHT because the deck column is
   capped at 480px and the hero padding is fixed, so the distance from the
   viewport edge to the cards holds as the window resizes. Anything low is
   measured from the BOTTOM, because hero height moves when the headline
   rewraps and top-anchored shapes down there got sliced by the marquee. */
.cs-deco--store  { top: 90px;  right: -220px; }
.cs-deco--leak   { top: 300px; right: -60px; }
.cs-deco--ask    { top: 62px;  right: 500px; }
.cs-deco--spike  { bottom: -34px; right: 430px; }
.cs-deco--signal { bottom: 300px; right: 640px; }

/* Rotation lives on the inner element so it composes with the float rather
   than being overwritten by it, and leaves the outer transform clean. */
.cs-deco--ask .cs-deco__in { --rot: -16deg; }
.cs-deco--leak .cs-deco__in { --rot: 12deg; }
.cs-deco--store .cs-deco__in { animation: none; }
@keyframes cs-float {
  0%, 100% { transform: translate3d(0, 0, 0) rotate(var(--rot, 0deg)); }
  50%      { transform: translate3d(0, -9px, 0) rotate(calc(var(--rot, 0deg) + 1.4deg)); }
}
@media (prefers-reduced-motion: reduce) {
  .cs-deco__in { animation: none; transform: rotate(var(--rot, 0deg)); }
}

.cs-h1__ln { white-space: nowrap; }
/* SIZED IN EM, NOT PX. BrandShape only ever writes a height attribute and
   takes its width from the viewBox, so a height in CSS rescales the whole
   shape. At a fixed 46px it was a third of the cap height on desktop and
   most of the line on a phone, which is what pushed the nowrap line over the
   edge and got the spark clipped. Tied to the type, it can no longer be the
   thing that overflows. */
.cs-h1__spark {
  display: inline-block;
  height: .62em;
  width: auto;
  vertical-align: 0.62em;
  margin-left: 2px;
  transform: rotate(-14deg) scaleX(-1);
}
.cs-hero__in { position: relative; z-index: 1; max-width: var(--cs-max); margin-inline: auto; display: grid; grid-template-columns: minmax(0,1fr) minmax(0,480px); gap: 56px; align-items: center; }

/* Endorsement sticker.
   What makes something read as a sticker rather than a label is the DIE CUT:
   a white rim following the shape, sitting on top of whatever is behind it.
   That is the box-shadow's first layer, with spread and no blur, so its radius
   tracks the element's own. The second layer is the lift off the page.

   INK FILL, LIME TYPE. It lived on the ink hero first, where the rim had to be
   the shape because a dark fill was invisible. On paper it can be the other way
   round, which is the arrangement it was modelled on in the first place: solid
   dark block, white rim cutting it out of the page. */
.cs-kicker {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  font-family: var(--cs-display), sans-serif;
  font-weight: 800;
  font-size: 13px;
  line-height: 1.3;
  letter-spacing: .03em;
  text-transform: uppercase;
  text-align: center;
  color: var(--lime);
  background: var(--ink);
  border-radius: 14px;
  padding: 11px 18px;
  box-shadow: 0 0 0 5px #fff, 0 10px 22px rgba(14, 28, 22, 0.18);
  transform: rotate(-3.6deg);
  transform-origin: 0% 50%;
  transition: transform .22s cubic-bezier(.2,.9,.2,1), box-shadow .22s ease-out;
  /* The wrapper is the hit target now, so the sticker must not swallow the
     pointer itself — otherwise leaving the rotated child while still inside
     the wrapper would drop the hover again. */
  pointer-events: none;
}
/* Static hit area. Sized by the sticker's UNTRANSFORMED box, which is what an
   inline-flex parent measures — the rotation and scale are invisible to it. */
.cs-kicker-hit {
  display: inline-flex;
  cursor: pointer;
}
/* Peels the other way and lifts a little. The rotation is the whole gag, so
   it crosses zero rather than just easing off: -3.6 to +2.4 reads as the
   sticker being nudged, where 0 would read as it being straightened. */
.cs-kicker-hit:hover .cs-kicker {
  transform: rotate(2.4deg) scale(1.03);
  box-shadow: 0 0 0 5px #fff, 0 16px 30px rgba(14, 28, 22, 0.26);
}
@media (prefers-reduced-motion: reduce) {
  .cs-kicker { transition: none }
  .cs-kicker-hit:hover .cs-kicker { transform: rotate(-3.6deg) }
}
/* Second line in white so the lockup reads as two halves rather than one
   block of lime, and the cursor closes it on the right. */
.cs-kicker em { font-style: normal; color: #fff; }
.cs-kicker__mark { flex: none; display: block; }
/* THE FLOOR IS WHAT THE LONGEST LINE CAN AFFORD, not a round number. The line
   is nowrap, so the type size IS the fit: at 50px "Growth the way" plus its
   spark measured about 315px against 276px of content on a 320px phone and
   was simply sliced off. 34px is that same line inside the narrowest case
   with room to spare, and the 7vw ramp is untouched, so nothing above about
   486px changes at all. */
/* ── THE HEADLINE IS MEASURED AGAINST ITS COLUMN, NOT THE WINDOW ─────────
 *
 * 7vw was always the wrong ruler and it took a 1200px laptop to show it. The
 * copy column is not the viewport: it is the viewport MINUS the 480px deck,
 * minus the 56px gutter, minus 120px of padding. At 1200px that leaves 544px
 * of column being sized as though it had 1200. "Growth the way" came out at
 * 84px, needed about 660px, and — being nowrap — simply carried on under the
 * recommendation cards.
 *
 * cqw is a percentage of the CONTAINER, so the same declaration is correct in
 * both layouts: beside the deck it reads the narrow column, stacked on a
 * phone it reads the full width, and neither case needs a breakpoint. 9.8cqw
 * puts the headline at its 104px ceiling once the page hits its 1600px cap,
 * which is where the design intends it.
 *
 * The vw line above it is the fallback and must stay. A browser without
 * container query units drops the cqw declaration and keeps that one. */
.cs-h1 { font-family: var(--cs-display), sans-serif; font-weight: 800; font-size: clamp(34px, 7vw, 104px); line-height: .88; letter-spacing: -.04em; color: #fff; }
.cs-hero__copy { container-type: inline-size; }
.cs-h1 { font-size: clamp(34px, 9.8cqw, 104px); }
.cs-h1 em { font-style: italic; color: var(--lime); }
/* THE FLOOR IS SET AGAINST THE HEADLINE, not read off a type scale. Both
   this and the h1 bottom out on a phone, and they do it at different rates:
   the pair is 104/21 on desktop, about five to one, and was 34/16 at 390px,
   barely two to one. A supporting line that close to the thing it supports
   stops reading as support.
   The ramp is 2.4vw rather than 1.9 for the same reason — at 1.9 the size sat
   flat from 790px down, so a tablet got the phone's setting. */
.cs-lede { font-size: clamp(15px, 2.4vw, 21px); line-height: 1.5; color: rgba(255,255,255,0.72); max-width: 560px; margin-top: 34px; }
.cs-acts { display: flex; align-items: center; gap: 16px; margin-top: 38px; flex-wrap: wrap; }

.cs-hero__cards { display: flex; flex-direction: column; gap: 14px; }

/* ── The deck ─────────────────────────────────────────────────────────── */
/*
   THE GAP IS A REAL CSS GAP. That is the whole design of this block. Three
   previous versions positioned each card absolutely and computed its offset,
   and every one of them shipped a spacing bug. Now the cards sit in normal
   flow inside .cs-track and the browser owns the 14px. It cannot be wrong.

   The track scrolls; the cards only slide sideways and rotate. Vertical is
   structural, horizontal is decoration, and they never touch each other.

   NO overflow: hidden. It would clip the sideways entry as well as the top
   and bottom. The mask does the vertical clipping only: it is sized 400% wide
   so the sides stay open, and 100% tall with no-repeat so anything above or
   below the two-card window has no mask and therefore no alpha. */
.cs-deck {
  position: relative;
  /* ── ONE KNOB FOR THE WHOLE DECK ──
     --deck-h comes in from the component as the two-card window measured off
     the declared card heights. Multiplying it here rather than setting a
     height inline is what lets a breakpoint resize the deck at all: an inline
     height would outrank every rule in this stylesheet. The track is scaled
     by the same factor, so the window and its contents can never disagree. */
  --deck-scale: 1;
  height: calc(var(--deck-h) * var(--deck-scale));
  /* WINGS ON ALL FOUR SIDES. Percentage/px padding is cancelled by an equal
     negative margin, so the MARGIN box stays exactly the card window and the
     grid lays out as if none of this were here, while the BORDER box — which
     is what overflow:hidden actually clips to — extends well past it.

     box-sizing is content-box here on purpose. The global border-box rule
     would fold the padding into the inline height and shrink the window.

     The vertical numbers are not guesses. Simulating every frame of the loop,
     the nearest card from the duplicate set is 756px above and 896px below the
     window at its closest, so anything under those is safe. 200/300 sits well
     inside that and leaves the loop seamless. */
  box-sizing: content-box;
  --bleed-x: 90%;
  --bleed-t: 200px;
  --bleed-b: 300px;
  padding: var(--bleed-t) var(--bleed-x) var(--bleed-b);
  margin: calc(-1 * var(--bleed-t)) calc(-1 * var(--bleed-x)) calc(-1 * var(--bleed-b));
  overflow: hidden;
  /* The wings sit over the headline, the CTAs and the marquee. Nothing in the
     deck is interactive, so let every click straight through. */
  pointer-events: none;
}
.cs-track {
  display: flex;
  flex-direction: column;
  gap: 14px;
  will-change: transform;
  animation: cs-track 25.8s cubic-bezier(0.16, 1, 0.3, 1) infinite;
}
/* NAME COLLISION, LEARNED THE HARD WAY. This was called .cs-card, which is
   also the Signals section's white tile further down this stylesheet. The deck
   cards silently inherited background:#fff, radius 24 and 34px of padding, so
   the lime stat sat in a white frame and every card lost 66px of vertical room
   and clipped its own content. Any class in here needs a .cs-deck prefix. */
/* Height from a variable, not from the element. --ch is the card's declared
   height; the narrow breakpoint swaps the whole set to --ch1, which it can
   only do because the declaration lives here rather than inline. */
.cs-deckwrap { position: relative; }
/* Behind the cards but in front of the hero. z-index -1 works because
   .cs-hero__in already carries z-index:1 and therefore opens a stacking
   context, so the negative index is measured inside that context and cannot
   fall behind the section's own background. Off by default: the wide hero
   has its own five-shape layer and does not need a sixth. */
/* Both hang off .cs-deckwrap, whose box is the card window, so they pin to
   the card's own corners rather than to the section. Offsets are negative on
   the right in both cases: the hero's 22px padding means anything under about
   -22px reaches the viewport edge, where the hero's overflow-x:clip cuts it —
   which is the point. A shape that stops neatly inside the margin reads as a
   sticker; one that runs off the edge reads as a layer the page sits on. */
.cs-deckdeco { position: absolute; display: none; z-index: -1; }
.cs-deckdeco--a { top: -30px; right: -16px; transform: rotate(8deg); }
.cs-deckdeco--b { bottom: -46px; right: -40px; transform: rotate(-14deg); }
.cs-deck__card {
  height: var(--ch);
  flex: none;
  transform-origin: 0% 130%;
  animation: cs-deck-card 25.8s cubic-bezier(0.16, 1, 0.3, 1) infinite;
  animation-fill-mode: both;
  /* AFTER the shorthand, never inside it. The shorthand resets delay to 0,
     so this has to be its own declaration and it has to come second. */
  animation-delay: var(--d);
}
.cs-deck__card > * { height: 100%; display: flex; flex-direction: column; justify-content: center; }

/* The track owns the RESTING position, so the gap is never this keyframe's
   business — the Y here is a runway either side of rest and is zero whenever
   a card is parked. A card lifts 180px from below as it fades in, sits, then
   drifts left and up as it fades out. A card is on screen for two of the six beats: it slides
   in over the first, sits still, then drifts left and fades. The fade is done
   by 33.3%, and the track does not start lifting it until 32.7%, so it has
   gone before it reaches the top edge — it never gets cut, it disappears. */
@keyframes cs-deck-card {
  0%      { transform: translate(60%, 180px) rotate(8deg); opacity: 0; }
  1.5%    { opacity: 1; }
  8%      { transform: translate(0, 0) rotate(0deg); opacity: 1; }
  27.5%   { transform: translate(0, 0) rotate(0deg); opacity: 1; }
  33.333% { transform: translate(-46%, -40px) rotate(-10deg); opacity: 0; }
  100%    { transform: translate(-46%, -40px) rotate(-10deg); opacity: 0; }
}

/* Motion sensitivity is a real accessibility need and a looping animation is
   the worst offender. Reduced motion gets the top two cards, sitting still,
   with the same gap — because the gap was never the animation's business. */
@media (prefers-reduced-motion: reduce) {
  .cs-deck { height: auto !important; padding: 0; margin: 0; overflow: visible; }
  .cs-track { animation: none; transform: none; }
  .cs-deck__card { animation: none; opacity: 1; transform: none; height: auto !important; }
  .cs-deck__card:nth-child(n+3) { display: none; }
}

.cs-reco { background: #fff; border-radius: 22px; padding: 24px 26px; overflow: hidden; }
.cs-reco__top { display: flex; justify-content: space-between; align-items: center; gap: 14px; }
.cs-reco__badge { display: flex; align-items: center; gap: 10px; font-weight: 700; font-size: 14px; color: #14211B; }
.cs-reco__icon { width: 30px; height: 30px; border-radius: 9px; background: #EAF0FF; display: flex; align-items: center; justify-content: center; }
.cs-reco__conf { font-family: var(--cs-mono), monospace; font-size: 11px; color: var(--blue); background: #EAF0FF; padding: 5px 10px; border-radius: 999px; white-space: nowrap; }
.cs-reco__h { font-family: var(--cs-display), sans-serif; font-weight: 700; font-size: 24px; line-height: 1.08; letter-spacing: -.02em; color: #14211B; margin-top: 14px; }
.cs-reco__b { font-size: 14px; line-height: 1.5; color: #55665C; margin-top: 9px; }
.cs-reco__foot { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; padding-top: 18px; flex-wrap: nowrap; }
.cs-reco__lbl { font-family: var(--cs-mono), monospace; font-size: 11px; color: #55665C; letter-spacing: .06em; }
.cs-reco__big { font-family: var(--cs-display), sans-serif; font-weight: 800; font-size: 36px; letter-spacing: -.02em; color: #14211B; line-height: 1; }
.cs-reco__btns { display: flex; gap: 8px; }

.cs-geo { background: var(--lime); border-radius: 22px; padding: 24px 26px; overflow: hidden; }
.cs-geo__lbl { font-family: var(--cs-mono), monospace; font-size: 11px; letter-spacing: .12em; color: #4a6b04; text-transform: uppercase; }
.cs-geo__row { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; margin-top: 8px; }
.cs-geo__h { font-family: var(--cs-display), sans-serif; font-weight: 800; font-size: 40px; letter-spacing: -.03em; color: var(--ink); line-height: 1; }
.cs-geo__bars { display: flex; align-items: flex-end; gap: 7px; }
.cs-geo__bars i { width: 16px; border-radius: 4px; display: block; }


/* ── Sections ────────────────────────────────────────────────────────── */
/* ── Marquee ─────────────────────────────────────────────────────────── */
@keyframes csmarquee { from { transform: translateX(0) } to { transform: translateX(-50%) } }
.cs-mq { border-top: 1px solid rgba(255,255,255,0.1); border-bottom: 1px solid rgba(255,255,255,0.1); padding: 22px 0; overflow: hidden; }
.cs-mq__track { display: flex; width: max-content; animation: csmarquee 32s linear infinite; }
.cs-mq__run { display: flex; align-items: center; gap: 54px; padding-right: 54px; }
.cs-mq__item { display: inline-flex; align-items: center; gap: 54px; font-family: var(--cs-display), sans-serif; font-weight: 700; font-size: 19px; color: rgba(255,255,255,0.45); white-space: nowrap; }
.cs-mq__item i { color: var(--lime); font-size: 15px; font-style: normal; }
@media (prefers-reduced-motion: reduce) { .cs-mq__track { animation: none } }

.cs-sec { padding: 96px 60px 100px; }
.cs-sec > *:not(.cs-secdeco) { max-width: var(--cs-max); margin-inline: auto; }
/* DO NOT REMOVE THIS CLASS. There are no section-level shapes any more, but
   the rule below is what makes the per-card shapes visible. They sit at
   z-index:-1 inside each card; without a stacking context between them and the
   section, -1 puts them behind the SECTION's background and they vanish
   entirely. Giving the section's children z-index:1 creates that context. */
.cs-sec--deco { position: relative; overflow-x: clip; }
.cs-sec--deco > * { position: relative; z-index: 1; }
/* Listed after the rule above so it wins on source order and drops back to 0.
   Same specificity, later declaration. */
.cs-secdeco { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 0; }
.cs-sdeco { position: absolute; display: block; }
/* Store carries 10/140 of viewBox padding, which is 34px at 480, so the
   visible edge sits 34px inside whatever the offset says. */
.cs-sdeco--slab { top: 24px; right: -150px; transform: scaleX(-1); }
.cs-sec--paper { background: var(--paper); }
.cs-sec--ink { background: var(--ink); }
.cs-sec--lime { background: var(--lime); }
.cs-sechead { display: flex; justify-content: space-between; align-items: flex-end; gap: 40px; margin-bottom: 48px; flex-wrap: wrap; }
/* Centred head, for sections whose content is a narrow centred block rather
   than a full-width grid. Written as .cs-sec > .cs-sechead--center so it beats
   the page-wide .cs-sec > *:not(.cs-secdeco) cap on specificity — a bare class
   would lose and the width would stay at 1600. */
.cs-sec > .cs-sechead--center { max-width: 760px; }
.cs-sechead--center { flex-direction: column; align-items: center; text-align: center; gap: 20px; margin-inline: auto; }
.cs-sechead--center .cs-sechead__note { max-width: 420px; }
.cs-sechead__side { display: flex; flex-direction: column; align-items: flex-start; gap: 26px; }
.cs-sechead__note--light { color: rgba(255,255,255,0.6); }
.cs-sechead__note { font-size: 17px; line-height: 1.55; color: #55665C; max-width: 320px; }
.cs-eyebrow { font-family: var(--cs-mono), monospace; font-size: 12px; letter-spacing: .16em; color: #55665C; text-transform: uppercase; margin-bottom: 18px; }
.cs-eyebrow--light { color: rgba(255,255,255,0.5); margin: 0 0 12px; }
.cs-eyebrow--dark { color: #4a6b04; margin-bottom: 20px; }
.cs-h2 { font-family: var(--cs-display), sans-serif; font-weight: 800; font-size: clamp(28px, 4.7vw, 68px); line-height: 1.0; letter-spacing: -.035em; color: #14211B; }
.cs-h2--light { color: #fff; }
/* Highlight.
   An inline background paints the font's FULL em box, which in Bricolage is
   about 1.28em. Set against a heading leading of .92 that box reached into the
   line above, which is what the overlap was. Two changes fix it: the heading
   gets a little more leading, and the highlight becomes an inline-block with
   its own tighter line-height so the painted box is a size we choose rather
   than one the font decides. Padding is asymmetric so what little overhang is
   left falls BELOW the baseline, where there is nothing to collide with. */
.cs-hl {
  position: relative;
  display: inline-block;
  line-height: .84;
  padding: .1em 12px .16em;
  background: var(--lime);
  color: var(--ink);
  border-radius: 10px;
}

.cs-g2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.cs-g3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px; }
.cs-g4 { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 20px; }

/* position:relative WITHOUT a z-index. That positions the shape children
   without creating a stacking context, which is what lets z-index:-1 put them
   behind the card's own background. Add a z-index here and every shape
   vanishes. */
.cs-card { position: relative; background: #fff; border-radius: 24px; padding: 34px 32px 32px; display: flex; flex-direction: column; gap: 18px; }
.cs-card--ink { background: var(--ink); }

/* Corner furniture. Only the part past the card's edge is ever seen, so the
   offsets are mostly negative and the sizes stay small. */
.cs-cd { position: absolute; z-index: -1; display: block; }
.cs-cd--a1 { top: -24px; left: -20px; }
.cs-cd--a2 { bottom: 48px; left: -40px; }
/* In FRONT of the card, not behind. Later in the sheet than .cs-cd, so this
   z-index wins over the -1 the others use. */
/* Rotation is listed FIRST so it reads in screen space. Written the other way
   round it would be applied inside the mirrored axes and tilt the wrong way. */
.cs-cd--b1 { top: -30px; right: -18px; transform: rotate(16deg) scaleX(-1); }
/* Pushed well clear of the edge so the whole tab shows, not a nub. */
.cs-cd--b2 { bottom: -40px; left: 44px; }
/* Rotation listed FIRST so it reads in screen space. After the two flips it
   would be applied inside doubly-mirrored axes and turn the wrong way. */
.cs-cd--c1 { bottom: -46px; left: 34px; transform: rotate(-20deg) scaleX(-1) scaleY(-1); }
/* Spark radiates up and left in its own artwork, so 180deg turns it to fire
   down and right, out of the corner it sits on. The extra 10deg stops it
   sitting square to the card. */
.cs-cd--c2 { bottom: -22px; right: -18px; transform: rotate(190deg); }
/* ── Card visuals ─────────────────────────────────────────────────────── */
/* Replaced three flat icons. Each loop is CSS only — no client bundle for a
   decoration — and each is aria-hidden, because it illustrates the heading
   rather than adding information. A screen reader should not read a fake
   event log out loud. Everything stops under reduced motion, holding its
   first frame rather than vanishing. */
.cs-viz { height: 150px; border-radius: 14px; padding: 14px; overflow: hidden; position: relative; }
.cs-viz--feed { background: #EEF2FF; }
.cs-viz--road { background: rgba(255,255,255,0.06); }
.cs-viz--ops  { background: #EAF0FF; animation: cs-opsbg 16s linear infinite; animation-delay: -6s; }

/* 1 · live thread */
.cs-viz__head { display: flex; align-items: center; gap: 7px; font-family: var(--cs-mono), monospace; font-size: 10.5px; letter-spacing: .13em; text-transform: uppercase; color: #7E8DA8; margin-bottom: 8px; }
.cs-viz__dot { width: 7px; height: 7px; border-radius: 50%; background: var(--blue); animation: cs-ping 1.8s ease-in-out infinite; }
@keyframes cs-ping { 0%,100% { opacity: .25; transform: scale(.8) } 50% { opacity: 1; transform: scale(1.2) } }

/* The rail runs the full window and the fade at each end is a mask, so the
   thread reads as a stream continuing past the box rather than a list that
   happens to be cut off. */
.cs-feed { position: relative; height: 96px; overflow: hidden;
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 8px, #000 76px, transparent 96px);
          mask-image: linear-gradient(180deg, transparent 0, #000 8px, #000 76px, transparent 96px); }
.cs-feed__rail { position: absolute; left: 9px; top: 0; bottom: 0; width: 2px; background: #D6E0F7; }
.cs-feed__track { position: relative; animation: cs-feed 11.6s cubic-bezier(.7,0,.3,1) infinite; animation-delay: -0.9s; }
.cs-feed__row { height: 32px; display: flex; align-items: center; gap: 10px; }
.cs-feed__nd { flex: none; width: 20px; height: 20px; border-radius: 50%; background: #fff; border: 1.5px solid #DCE5F7; display: flex; align-items: center; justify-content: center; color: var(--blue); }
.cs-feed__nd svg { width: 11px; height: 11px; }
.cs-feed__nd.is-buy { background: var(--blue); border-color: var(--blue); color: #fff; }
.cs-feed__lab { font-size: 13px; color: #33415C; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Three chip tones, and the difference is meaning rather than decoration:
   where they came FROM, what they DID, and what it was WORTH. Only the last
   gets colour, so the payoff row is the one thing the eye lands on. */
.cs-feed__chip { margin-left: auto; flex: none; font-family: var(--cs-mono), monospace; font-size: 9.5px; letter-spacing: .07em; text-transform: uppercase; padding: 4px 8px; border-radius: 20px; background: #fff; color: #5C7BB5; white-space: nowrap; }
.cs-feed__chip.is-src { background: #E2E8FC; color: #2F5BB5; }
.cs-feed__chip.is-buy { background: var(--lime); color: #2A3A10; }
/* Four rows rendered twice, stepped one row a beat. -128px is one full set. */
@keyframes cs-feed {
  0%,20%   { transform: translateY(0) }
  25%,45%  { transform: translateY(-32px) }
  50%,70%  { transform: translateY(-64px) }
  75%,95%  { transform: translateY(-96px) }
  100%     { transform: translateY(-128px) }
}

/* 2 · roadmap reordering */
.cs-viz--road { display: block; }
.cs-road__n { position: absolute; left: 14px; font-family: var(--cs-mono), monospace; font-size: 10px; color: rgba(255,255,255,.35); line-height: 30px; }
.cs-road__n:nth-of-type(1) { top: 24px }
.cs-road__n:nth-of-type(2) { top: 58px }
.cs-road__n:nth-of-type(3) { top: 92px }
.cs-road__job { position: absolute; top: 24px; left: 44px; right: 14px; height: 30px; display: flex; align-items: center; padding: 0 12px; border-radius: 8px; font-size: 12px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; animation: cs-road 8.4s cubic-bezier(.7,0,.3,1) infinite; }
/* THE LIME BELONGS TO THE SLOT, NOT THE JOB. It is animated on the same
   keyframe as the position, so whichever job is at rank one is the lime one.
   An earlier version put the colour on a fixed element, which made it travel
   down the list with that job and read as "this task is special" rather than
   "this is what we do next". The colour is a function of rank, so it has to
   move with rank. */
@keyframes cs-road {
  0%, 26%  { transform: translateY(0);    background: var(--lime); color: var(--ink); }
  33%, 59% { transform: translateY(34px); background: rgba(255,255,255,.1); color: #fff; }
  66%, 92% { transform: translateY(68px); background: rgba(255,255,255,.1); color: #fff; }
  100%     { transform: translateY(0);    background: var(--lime); color: var(--ink); }
}

/* 3 · one job moving through delivery */
.cs-ops__top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.cs-ops__task { font-size: 13px; font-weight: 700; color: #14211B; }
.cs-ops__live { display: flex; align-items: center; gap: 6px; font-family: var(--cs-mono), monospace; font-size: 10px; letter-spacing: .13em; text-transform: uppercase; color: rgba(20,33,27,.45); }
.cs-ops__live i { width: 6px; height: 6px; border-radius: 50%; animation: cs-ping 1.8s ease-in-out infinite, cs-opshue 16s linear infinite; animation-delay: 0s, -6s; }

/* Stage on the left, people on the right. The initials sit beside the comment
   circle rather than beside the badge, so the right-hand cluster reads as one
   thing — who is on it and where the conversation is — instead of the badge
   carrying two unrelated pieces of furniture.

   Everything is stacked absolutely so the box never resizes: "In QA" and
   "Design in progress" are a long way apart. */
.cs-ops__row { position: relative; display: block; height: 32px; margin-top: 16px; }
.cs-ops__who { position: absolute; right: 32px; top: 3px; width: 26px; height: 26px; border-radius: 50%; background: #14211B; color: #fff; font-family: var(--cs-body), sans-serif; font-size: 10px; font-weight: 600; letter-spacing: .02em; display: flex; align-items: center; justify-content: center; opacity: 0; animation: cs-who 16s cubic-bezier(.16,1,.3,1) infinite; }
.cs-ops__badge { position: absolute; left: 0; top: 0; font-style: normal; font-family: var(--cs-display), sans-serif; font-weight: 700; font-size: 14px; letter-spacing: -.01em; white-space: nowrap; padding: 7px 13px; border-radius: 20px; opacity: 0; animation: cs-stage 16s cubic-bezier(.16,1,.3,1) infinite; }
/* Rises in, settles, leaves upward. Opacity alone read as a slideshow. */
@keyframes cs-stage {
  0%       { opacity: 0; transform: translateY(9px) scale(.96); }
  3%, 10%  { opacity: 1; transform: translateY(0) scale(1); }
  12.5%    { opacity: 0; transform: translateY(-9px) scale(.98); }
  100%     { opacity: 0; transform: translateY(-9px) scale(.98); }
}
/* The person pops rather than rises, and lands a fraction after the badge. A
   circle travelling vertically alongside a pill travelling vertically read as
   one object in two pieces; scaling from the centre makes it its own arrival. */
@keyframes cs-who {
  0%       { opacity: 0; transform: scale(.5); }
  3.5%     { opacity: 1; transform: scale(1.09); }
  6%, 10%  { opacity: 1; transform: scale(1); }
  12.5%    { opacity: 0; transform: scale(.72); }
  100%     { opacity: 0; transform: scale(.72); }
}

/* Comment affordance. A CIRCLE WITH A DOT, NOT A COUNT IN A SECOND CIRCLE —
   a number sitting where a badge goes reads as "13 unread" rather than "13
   comments", which is two grammars at once. The dot says there is a
   conversation; the number would have to be real to be worth carrying. */
/* THE CIRCLE NEVER MOVES. It is a permanent affordance — a place the
   conversation lives — so blinking it in and out each stage said the wrong
   thing. Only the dot animates, which is what "something new" actually is. */
.cs-ops__cmt { position: absolute; right: 0; top: 3px; width: 26px; height: 26px; border-radius: 50%; background: #fff; display: flex; align-items: center; justify-content: center; color: #14211B; }
.cs-ops__cmt svg { width: 13px; height: 13px; }
/* Two animations on one element: colour tracks the stage over the full 16s,
   while the pop runs on a one-beat loop so it lands on every handover. */
.cs-ops__cmt i { position: absolute; top: -1px; right: -1px; width: 8px; height: 8px; border-radius: 50%; animation: cs-opshue 16s linear infinite, cs-cmtdot 2s cubic-bezier(.16,1,.3,1) infinite; animation-delay: -6s, 0s; }
@keyframes cs-cmtdot {
  0%   { opacity: 0; transform: scale(0); }
  20%  { opacity: 1; transform: scale(1.25); }
  34%  { opacity: 1; transform: scale(1); }
  84%  { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(.4); }
}

/* ONE bar stepping its width, with dividers painted over it. Eight separate
   segments cannot share a keyframe: one that lights at 3/8 and stays lit to the
   end is not a time-shifted copy of one that lights at 4/8.

   Track and ticks are translucent rather than fixed colours, so they hold up
   against a box background that changes underneath them. */
.cs-ops__bar { position: relative; margin-top: 16px; height: 8px; border-radius: 5px; background: rgba(20,33,27,.09); overflow: hidden; }
.cs-ops__bar > i { position: relative; display: block; height: 100%; border-radius: 5px; animation: cs-ops 16s cubic-bezier(.7,0,.3,1) infinite; animation-delay: -6s; }
/* Highlight at the leading edge, on its own BEAT-length loop. Because the beat
   divides the duration exactly, it stays in phase with the steps for free. */
.cs-ops__bar > i::after { content: ""; position: absolute; right: 0; top: 0; bottom: 0; width: 22px; background: linear-gradient(90deg, transparent, rgba(255,255,255,.9)); animation: cs-opshead 2s ease-out infinite; }
@keyframes cs-opshead { 0% { opacity: .95 } 55%, 100% { opacity: 0 } }
.cs-ops__ticks { position: absolute; inset: 0; }
.cs-ops__ticks b { position: absolute; top: 0; bottom: 0; width: 2px; margin-left: -1px; background: rgba(255,255,255,.9); }

.cs-ops__hrs { position: relative; display: block; height: 14px; margin-top: 11px; }
.cs-ops__hrs em { position: absolute; left: 0; top: 0; font-style: normal; font-family: var(--cs-mono), monospace; font-size: 10.5px; color: rgba(20,33,27,.45); white-space: nowrap; opacity: 0; animation: cs-stagefade 16s linear infinite; }
/* Hours fade rather than move. Rolling them pulled the eye to the least
   important thing in the box, and implied a live count that is not one. */
@keyframes cs-stagefade { 0% { opacity: 0 } 1.5%, 11% { opacity: 1 } 12.5%, 100% { opacity: 0 } }

/* Box tint, bar colour and every accent are generated from STAGES so the
   palette lives in one place. */
@keyframes cs-opsbg {
  0.0%, 10.0% { background-color: #EAF0FF; }
  12.5%, 22.5% { background-color: #EAF0FF; }
  25.0%, 35.0% { background-color: #EFEBFE; }
  37.5%, 47.5% { background-color: #EFEBFE; }
  50.0%, 60.0% { background-color: #FFF1EB; }
  62.5%, 72.5% { background-color: #FFF1EB; }
  75.0%, 85.0% { background-color: #F4FBDC; }
  87.5%, 97.5% { background-color: #F4FBDC; }
  100% { background-color: #EAF0FF; }
}
@keyframes cs-opshue {
  0.0%, 10.0% { background-color: #2F6BFF; }
  12.5%, 22.5% { background-color: #2F6BFF; }
  25.0%, 35.0% { background-color: #6C4CF1; }
  37.5%, 47.5% { background-color: #6C4CF1; }
  50.0%, 60.0% { background-color: #FF5A2C; }
  62.5%, 72.5% { background-color: #FF5A2C; }
  75.0%, 85.0% { background-color: #D3F502; }
  87.5%, 97.5% { background-color: #D3F502; }
  100% { background-color: #2F6BFF; }
}
@keyframes cs-ops {
  0.0%, 10.0% { width: 12.5%; background-color: #2F6BFF; }
  12.5%, 22.5% { width: 25.0%; background-color: #2F6BFF; }
  25.0%, 35.0% { width: 37.5%; background-color: #6C4CF1; }
  37.5%, 47.5% { width: 50.0%; background-color: #6C4CF1; }
  50.0%, 60.0% { width: 62.5%; background-color: #FF5A2C; }
  62.5%, 72.5% { width: 75.0%; background-color: #FF5A2C; }
  75.0%, 85.0% { width: 87.5%; background-color: #D3F502; }
  87.5%, 97.5% { width: 100.0%; background-color: #D3F502; }
  100% { width: 12.5%; background-color: #2F6BFF; }
}

@media (prefers-reduced-motion: reduce) {
  .cs-feed__track, .cs-road__job, .cs-viz__dot, .cs-viz--ops, .cs-ops__live i,
  .cs-ops__bar > i, .cs-ops__bar > i::after, .cs-ops__cmt i,
  .cs-ops__badge, .cs-ops__who, .cs-ops__hrs em { animation: none; }
  .cs-ops__cmt i { background: #6C4CF1; opacity: 1; transform: none }
  .cs-ops__bar > i::after { opacity: 0 }
  .cs-ops__live i { background: #6C4CF1 }
  .cs-road__job:nth-of-type(5) { transform: translateY(34px) }
  .cs-road__job:nth-of-type(6) { transform: translateY(68px) }
  /* Holds on Design in progress: a more representative still than Requested. */
  .cs-viz--ops { background: #EFEBFE }
  .cs-ops__bar > i { width: 50%; background: #6C4CF1 }
  .cs-ops__badge, .cs-ops__who, .cs-ops__hrs em { opacity: 0; transform: none }
  .cs-ops__badge:nth-of-type(4), .cs-ops__who:nth-of-type(4), .cs-ops__hrs em:nth-of-type(4) { opacity: 1 }
}
.cs-card__h { font-family: var(--cs-display), sans-serif; font-weight: 700; font-size: 30px; line-height: 1.05; letter-spacing: -.02em; color: #14211B; }
.cs-card--ink .cs-card__h { color: #fff; }
.cs-card__b { font-size: 16px; line-height: 1.55; color: #55665C; }
.cs-card--ink .cs-card__b { color: rgba(255,255,255,0.7); }
.cs-card__stat { margin-top: auto; padding-top: 20px; border-top: 1px solid #EEF1EA; display: flex; align-items: baseline; gap: 10px; }
.cs-card--ink .cs-card__stat { border-color: rgba(255,255,255,0.12); }
.cs-card__stat b { font-family: var(--cs-display), sans-serif; font-weight: 800; font-size: 34px; color: #14211B; letter-spacing: -.02em; }
.cs-card__stat span { font-size: 14px; color: #55665C; }
.cs-card--ink .cs-card__stat span { color: rgba(255,255,255,0.7); }

/* ── How it works ─────────────────────────────────────────────────────── */
/* One 15s clock drives the rail, the dots and the panels. Everything uses the
   same delay function, so adding a sixth step means changing HOW_DUR and
   nothing else. */
/* Written as .cs-sec > .cs-how, not .cs-how. The section-wide cap above is
   .cs-sec > *:not(.cs-secdeco), which scores two class selectors; a bare
   .cs-how scores one and loses, so the narrower cap silently did nothing.

   1000 rather than the page's 1600 because the panel is the thing being sized
   here, not the section. At full measure the card ran past 800px wide for two
   short lines of content and looked empty. */
.cs-sec > .cs-how { max-width: 1000px; }
/* How it works decoration. Outer margins only; see the note in the component.
   The slab is tonal rather than coloured, so it reads as texture behind the
   white panel instead of a fourth thing to look at. */
.cs-hdeco--slab { top: 210px; right: -190px; transform: scaleX(-1) rotate(6deg); }
.cs-hdeco--ask { top: 96px; left: -26px; transform: rotate(-14deg); }
/* Hung off the rail so it stays at the foot of the line however tall the steps
   grow. left matches .cs-how__rail padding-left minus the shape's own inset. */
.cs-hdeco--spike { position: absolute; left: -22px; top: 100%; margin-top: 6px; transform: rotate(8deg); }
.cs-how { margin-inline: auto; display: grid; grid-template-columns: minmax(0, 290px) minmax(0, 1fr); gap: 48px; align-items: center; }

.cs-how__rail { position: relative; padding-left: 30px; }
.cs-how__track { position: absolute; left: 5px; top: 10px; bottom: 10px; width: 2px; background: rgba(255,255,255,.12); border-radius: 2px; overflow: hidden; }
.cs-how__track i { position: absolute; left: 0; top: 0; width: 2px; background: var(--lime); transition: height .55s cubic-bezier(.7,0,.3,1); }
/* Class-driven, not keyframe-driven: the loop is a timer in the component, so
   an automatic advance and a click take the same path and look identical.
   Wanting both is exactly what forced this out of pure CSS. */
.cs-how__step { position: relative; display: block; width: 100%; text-align: left; padding: 13px 0; opacity: .34; cursor: pointer; background: none; border: 0; border-radius: 8px; font: inherit; color: inherit; transition: opacity .35s ease; }
.cs-how__step:hover { opacity: .7; }
.cs-how__step.is-on, .cs-how__step.is-on:hover { opacity: 1; }
.cs-how__step:focus-visible { outline: 2px solid var(--lime); outline-offset: 4px; }
.cs-how__step em { position: absolute; left: -30px; top: 20px; width: 12px; height: 12px; border-radius: 50%; background: #3A4A42; transition: background .35s ease, transform .35s cubic-bezier(.16,1,.3,1); }
.cs-how__step.is-on em { background: var(--lime); transform: scale(1.2); }
.cs-how__step b { display: block; font-family: var(--cs-mono), monospace; font-size: 10px; letter-spacing: .13em; color: rgba(255,255,255,.45); margin-bottom: 4px; }
.cs-how__step h3 { font-family: var(--cs-display), sans-serif; font-weight: 700; font-size: 19px; line-height: 1.25; letter-spacing: -.015em; color: #fff; }

/* Panels are stacked so the box never resizes between states. */
.cs-how__stage { position: relative; min-height: 320px; }
.cs-how__pan { position: absolute; inset: 0; opacity: 0; transform: translateY(12px) scale(.985); transition: opacity .45s ease, transform .55s cubic-bezier(.16,1,.3,1); }
.cs-how__pan.is-on { opacity: 1; transform: none; }
/* The hidden attribute is for assistive tech. Without this override it would
   also kill the transition, because display:none cannot animate. */
.cs-how__pan[hidden] { display: block; }
/* Recommendation card. Bleeds to the edges of the panel, so it reads as the
   modal itself rather than a modal pictured inside a card. */
/* NO BOX BEHIND EITHER HALF. A hairline shows wherever one rounded box is
   painted on top of another: two 16px radii never antialias identically and
   the lower one peeks at the curve. This was fixed twice by moving the white
   around, which just moved the seam. The cause is the stacking, so there is no
   background on the container at all — the header paints its own top corners
   and the body its own bottom corners, each straight onto the section. */
.cs-how__card:has(.cs-how__rec) { background: transparent; padding: 0; gap: 0; }
.cs-how__rec { background: transparent; display: flex; flex-direction: column; height: 100%; }
/* Lifted off the section ink so the card separates. #14211B was only a shade
   above the #0E1C16 background and the header read as part of the page. */
.cs-how__rechd { background: #1C3026; border-radius: 16px 16px 0 0; padding: 22px 26px; display: flex; flex-direction: column; align-items: flex-start; gap: 13px; }
.cs-how__k--onink { display: inline-flex; align-items: center; gap: 8px; color: rgba(255,255,255,.5); }
.cs-how__k--onink svg { width: 13px; height: 13px; color: var(--lime); }
.cs-how__rect { font-family: var(--cs-display), sans-serif; font-weight: 700; font-size: 23px; line-height: 1.22; letter-spacing: -.02em; color: #fff; }
.cs-how__recchip { opacity: 0; transform: translateY(6px); transition: opacity .4s ease, transform .5s cubic-bezier(.16,1,.3,1); transition-delay: 0s; }
.cs-how__pan.is-on .cs-how__recchip { opacity: 1; transform: none; transition-delay: .18s; }
.cs-how__recbd { background: #fff; border-radius: 0 0 16px 16px; flex: 1; display: flex; flex-direction: column; justify-content: center; gap: 20px; padding: 22px 26px; }
.cs-how__recbd > div { display: flex; flex-direction: column; gap: 9px; }
.cs-how__data { padding-top: 18px; border-top: 1px solid #EEF1F6; }
.cs-how__bars { display: flex; flex-direction: column; gap: 10px; }
.cs-how__wbar { display: block; height: 14px; border-radius: 8px; background: #EEF1F6; overflow: hidden; }
/* Grows when the panel activates, like every other state in this section. */
.cs-how__wbar i { display: block; height: 100%; width: 0; border-radius: 8px; background: #C6D2E4; transition: width .8s cubic-bezier(.7,0,.3,1); transition-delay: 0s; }
.cs-how__wbar.is-hi i { background: var(--blue); }
.cs-how__pan.is-on .cs-how__wbar i { width: var(--w); transition-delay: .3s; }
.cs-how__pan.is-on .cs-how__wbar.is-hi i { transition-delay: .42s; }
.cs-how__recwhy { font-size: 15px; line-height: 1.55; color: #55665C; }

/* Kick-off card. Rows are TRANSITIONS, not keyframes, so they replay every
   time the panel becomes active — including when you click straight to it.
   The stagger is a per-row custom property applied only in the is-on state, so
   entry cascades and exit leaves all at once rather than unwinding backwards. */
.cs-how__k--faint { color: #B7C2D6; }
.cs-how__kick { display: grid; grid-template-columns: minmax(0, 168px) minmax(0, 1fr); gap: 30px; align-items: start; }
.cs-how__kickside { display: flex; flex-direction: column; gap: 22px; }
.cs-how__facts { display: flex; flex-direction: column; gap: 8px; }
.cs-how__fact { display: flex; align-items: center; gap: 10px; font-size: 13px; color: #33415C; opacity: 0; transform: translateY(6px); transition: opacity .4s ease, transform .5s cubic-bezier(.16,1,.3,1); transition-delay: 0s; }
.cs-how__pan.is-on .cs-how__fact { opacity: 1; transform: none; transition-delay: var(--d, 0s); }
.cs-how__srcmark { width: 22px; height: 22px; border-radius: 6px; display: flex; align-items: center; justify-content: center; flex: none; font-style: normal; font-family: var(--cs-mono), monospace; font-size: 12px; font-weight: 600; }
.cs-how__srcmark svg { width: 12px; height: 12px; }
.cs-how__srcmark.is-goal, .cs-how__key i.is-goal { background: #EEF9C6; color: #5C7A18; }
.cs-how__srcmark.is-ask, .cs-how__key i.is-ask { background: #EFEBFE; color: #6C4CF1; }
.cs-how__srcmark.is-measured, .cs-how__key i.is-measured { background: #EAF0FF; color: #2F6BFF; }
/* Stacked, not inline: it lives in the narrow left column now, beneath the
   person, which is otherwise dead space. */
.cs-how__key { display: flex; flex-direction: column; gap: 9px; font-size: 11.5px; color: #8A968F; }
.cs-how__key i { display: inline-block; width: 9px; height: 9px; border-radius: 3px; margin-right: 5px; vertical-align: -1px; }
.cs-how__card { background: #fff; border-radius: 16px; padding: 22px 24px; height: 100%; display: flex; flex-direction: column; justify-content: center; gap: 13px; }
.cs-how__k { font-family: var(--cs-mono), monospace; font-size: 10px; letter-spacing: .14em; text-transform: uppercase; color: #8496B5; }
.cs-how__h { font-family: var(--cs-display), sans-serif; font-weight: 700; font-size: 20px; line-height: 1.25; letter-spacing: -.02em; color: #14211B; }
.cs-how__url { display: flex; align-items: center; gap: 11px; background: #F1F4F9; border-radius: 10px; padding: 12px 14px; font-family: var(--cs-mono), monospace; font-size: 13px; color: #33415C; }
.cs-how__tick { width: 22px; height: 22px; border-radius: 50%; background: var(--lime); display: flex; align-items: center; justify-content: center; flex: none; }
.cs-how__tick svg { width: 12px; height: 12px; color: var(--ink); }
.cs-how__pill { align-self: flex-start; font-family: var(--cs-mono), monospace; font-size: 10px; letter-spacing: .1em; text-transform: uppercase; padding: 6px 12px; border-radius: 20px; background: #EFEBFE; color: #4A34B8; }
.cs-how__pill--lime { background: var(--lime); color: #2A3A10; }
.cs-how__plan { display: flex; flex-direction: column; gap: 9px; }
.cs-how__grp { font-family: var(--cs-mono), monospace; font-size: 9.5px; letter-spacing: .13em; text-transform: uppercase; color: #B7C2D6; }
.cs-how__just { margin-left: auto; font-style: normal; font-family: var(--cs-mono), monospace; font-size: 9.5px; letter-spacing: .08em; text-transform: uppercase; color: #5C7A18; background: rgba(255,255,255,.55); padding: 4px 9px; border-radius: 20px; }
.cs-how__agreed { display: flex; align-items: center; gap: 9px; font-size: 12px; font-weight: 600; color: #5C7A18; opacity: 0; transform: translateY(5px); transition: opacity .4s ease, transform .5s cubic-bezier(.16,1,.3,1); transition-delay: 0s; }
.cs-how__pan.is-on .cs-how__agreed { opacity: 1; transform: none; transition-delay: 1.05s; }
/* Rows below slide down to open a gap, then the new job drops into it. The
   shift is a transform so nothing reflows and the rows below stay put. */
.cs-how__job--shift { transform: translateY(-54px); transition: transform .55s cubic-bezier(.16,1,.3,1); transition-delay: 0s; }
.cs-how__pan.is-on .cs-how__job--shift { transform: none; transition-delay: .2s; }
.cs-how__job--drop { opacity: 0; transform: translateY(-12px) scale(.98); transition: opacity .4s ease, transform .55s cubic-bezier(.16,1,.3,1); transition-delay: 0s; }
.cs-how__pan.is-on .cs-how__job--drop { opacity: 1; transform: none; transition-delay: .55s; }
.cs-how__job { display: flex; align-items: center; gap: 11px; padding: 9px 13px; border-radius: 10px; background: #F1F4F9; font-size: 13px; color: #33415C; }
.cs-how__job span { font-family: var(--cs-mono), monospace; font-size: 10px; color: #8496B5; }
.cs-how__job.is-top { background: var(--lime); color: var(--ink); font-weight: 600; }
.cs-how__job.is-top span { color: #5C7A18; }
.cs-how__job .cs-how__av { margin-left: auto; }
.cs-how__top { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
/* People furniture. The section opens and closes on a face rather than a
   dashboard, which is the whole point of the rework. */
.cs-how__who { display: flex; align-items: center; gap: 13px; }
.cs-how__who b { display: block; font-size: 14px; font-weight: 700; color: #14211B; }
.cs-how__who span { font-size: 12px; color: #7A8A80; }
.cs-how__av--lg { width: 42px; height: 42px; font-size: 13px; }
.cs-how__faces { display: flex; }
.cs-how__faces .cs-how__av { margin-left: -9px; box-shadow: 0 0 0 2.5px #fff; }
.cs-how__faces .cs-how__av:first-child { margin-left: 0; }
.cs-how__av { width: 24px; height: 24px; border-radius: 50%; background: var(--ink); color: #fff; font-size: 9.5px; font-weight: 600; font-style: normal; display: flex; align-items: center; justify-content: center; flex: none; }
.cs-how__flighth { font-family: var(--cs-display), sans-serif; font-weight: 700; font-size: 20px; line-height: 1.25; letter-spacing: -.02em; color: #14211B; }
.cs-how__live { display: inline-flex; align-items: center; gap: 7px; font-family: var(--cs-mono), monospace; font-size: 10px; letter-spacing: .13em; text-transform: uppercase; color: #A8B2C4; }
.cs-how__live i { width: 6px; height: 6px; border-radius: 50%; background: #6C4CF1; animation: cs-ping 1.8s ease-in-out infinite; }
.cs-how__flightmid { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.cs-how__flightwho { display: flex; align-items: center; gap: 9px; }
.cs-how__cmt { position: relative; width: 27px; height: 27px; border-radius: 50%; background: #F1F4F9; display: flex; align-items: center; justify-content: center; color: #33415C; }
.cs-how__cmt svg { width: 13px; height: 13px; }
/* Its own beat-length loop, so it pops on arrival and again as the panel ends. */
.cs-how__cmt i { position: absolute; top: -1px; right: -1px; width: 8px; height: 8px; border-radius: 50%; background: #6C4CF1; animation: cs-cmtdot 3s cubic-bezier(.16,1,.3,1) infinite; }
.cs-how__feed { border-top: 1px solid #EEF1F6; padding-top: 15px; display: flex; flex-direction: column; gap: 11px; }
.cs-how__fr { display: flex; align-items: center; gap: 11px; font-size: 13.5px; color: #33415C; opacity: 0; transform: translateY(6px); transition: opacity .4s ease, transform .5s cubic-bezier(.16,1,.3,1); transition-delay: 0s; }
.cs-how__pan.is-on .cs-how__fr { opacity: 1; transform: none; transition-delay: var(--d, 0s); }
/* Only the newest entry is filled, so the eye lands on what just happened. */
.cs-how__fr > i { width: 8px; height: 8px; border-radius: 50%; background: #DCE3ED; flex: none; }
.cs-how__fr > i.is-now { background: #6C4CF1; }
.cs-how__fr em { margin-left: auto; font-style: normal; font-family: var(--cs-mono), monospace; font-size: 10.5px; color: #A8B2C4; }
.cs-how__bar { position: relative; height: 9px; border-radius: 5px; background: #EDE7FD; overflow: hidden; }
.cs-how__bar i { display: block; height: 100%; width: 62%; border-radius: 5px; background: #6C4CF1; }
.cs-how__bar b { position: absolute; top: 0; bottom: 0; width: 2px; margin-left: -1px; background: #fff; }
.cs-how__ship { font-family: var(--cs-mono), monospace; font-size: 9.5px; letter-spacing: .09em; text-transform: uppercase; padding: 5px 11px; border-radius: 20px; background: var(--lime); color: #2A3A10; }
.cs-how__ba { display: flex; flex-direction: column; gap: 11px; }
.cs-how__brow { display: grid; grid-template-columns: 56px minmax(0, 1fr); align-items: center; gap: 14px; }
.cs-how__brow span { font-family: var(--cs-mono), monospace; font-size: 9.5px; letter-spacing: .1em; text-transform: uppercase; color: #A8B2C4; }
.cs-how__brow b { display: block; height: 15px; border-radius: 8px; background: #EEF1F6; overflow: hidden; }
.cs-how__brow b i { display: block; height: 100%; width: 0; border-radius: 8px; background: #C6D2E4; transition: width .85s cubic-bezier(.7,0,.3,1); transition-delay: 0s; }
.cs-how__pan.is-on .cs-how__brow b i { width: var(--w); transition-delay: var(--d, 0s); }
.cs-how__brow.is-after span { color: #5C7A18; }
.cs-how__brow.is-after b i { background: var(--lime); }
.cs-how__revfoot { display: flex; align-items: center; gap: 13px; font-size: 13.5px; color: #33415C; }
/* The loop restarting. Arrives last, well after the result has landed. */
.cs-how__next { display: flex; align-items: center; gap: 11px; font-size: 13px; color: #55665C; background: #F4F6FA; border-radius: 11px; padding: 12px 14px; opacity: 0; transform: translateY(6px); transition: opacity .4s ease, transform .5s cubic-bezier(.16,1,.3,1); transition-delay: 0s; }
.cs-how__pan.is-on .cs-how__next { opacity: 1; transform: none; transition-delay: 1.2s; }
.cs-how__next svg { width: 15px; height: 15px; color: #5C7A18; flex: none; }
.cs-how__next em { margin-left: auto; font-style: normal; font-family: var(--cs-mono), monospace; font-size: 9.5px; letter-spacing: .08em; text-transform: uppercase; color: #5C7A18; background: #EEF9C6; padding: 4px 9px; border-radius: 20px; }

@media (prefers-reduced-motion: reduce) {
  /* The component stops advancing by itself under reduced motion, so all that
     is left here is taking the movement out of the state change. */
  .cs-how__track i, .cs-how__step, .cs-how__step em, .cs-how__pan { transition: none; }
  .cs-how__pan { transform: none; }
}

/* ── Human craft ──────────────────────────────────────────────────────── */
/* Statement beside one picture. A full-bleed photographic band with an ink
   block over it was built first and swapped out: after three animated sections
   the page needs a plain one, and the band was another big visual gesture
   rather than a rest. */
.cs-craft { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 60px; align-items: center; }

/* ── THE LIME BOX FOLLOWS THE HEADING, SO FIX THE HEADING ────────────────
 *
 * .cs-hl is an inline-block, which means the instant its text wraps it stops
 * hugging the words and becomes ONE rectangle the width of the column — so a
 * short last line leaves a slab of empty lime beside it. That is the thing
 * that looks wrong, but it is a symptom: the highlight only wraps because
 * the heading is too big for the column it is in.
 *
 * And it is too big for the same reason the h1 was overlapping the deck.
 * .cs-h2 is 4.7vw, measured against the WINDOW, while this column is half
 * the grid minus a 60px gutter. Between about 1080 and 1560 — most laptops —
 * "Years of experience." needed more width than the column had and broke in
 * two. Above and below that band it happens to fit, which is why it looks
 * like an intermittent fault rather than a sizing error.
 *
 * cqw measures the column instead, so the heading shrinks with the space it
 * actually occupies and the line stays whole at every width. The highlight
 * then hugs its text again on its own, with no change to .cs-hl at all —
 * and the other two headings, which are not in a narrow column, keep the
 * viewport-based size they were designed with.
 *
 * 8.6 is set so the longest line clears the column with a little room. If
 * the copy here ever gets longer, this is the number to lower.
 */
.cs-craft__say { container-type: inline-size; }
.cs-craft__say .cs-h2 { font-size: clamp(28px, 8.6cqw, 68px); }
/* Percentages are of the 1600 grid, not the viewport, so these hold their
   relationship to the text and the photo as the window changes. */
.cs-crd { position: absolute; z-index: -1; display: block; }
/* Tonal, not coloured. It is the only shape big enough to compete with the
   photograph, so it stays a shade off Paper and reads as texture. */
.cs-crd--slab { top: -72px; left: 45%; transform: scaleX(-1); }
.cs-craft__mark { position: absolute; left: 100%; bottom: 2px; margin-left: 46px; transform: rotate(-14deg); }
.cs-crd--ask { bottom: 34px; right: -34px; transform: scaleX(-1) rotate(12deg); }
/* Same orientation as the hero spark coming off "growth": flipped, then tipped
   back, so the arms fan up and away from the corner rather than into it. */
/* In em for the same reason as the h1 spark: the h2 is a clamp, so a fixed
   44px shape is a flourish on the corner at 68px and a third of the line at
   28px. Its offsets are in em too, or the shape shrinks while the gap it
   sits in does not. */
.cs-hl__spark { position: absolute; left: 100%; bottom: 100%; height: .62em; width: auto; margin: 0 0 -.26em -.15em; transform: rotate(-14deg) scaleX(-1); }
.cs-craft__p { position: relative; font-size: 17px; line-height: 1.6; color: #55665C; max-width: 460px; margin-top: 22px; }
.cs-craft__figure { position: relative; }
/* Climbs up and to the right out of the corner. z-index -1 so the photograph
   crops its lower end and it reads as emerging from behind rather than sitting
   on top. */
.cs-craft__trail { position: absolute; left: 100%; bottom: 100%; margin: 0 0 -74px -74px; transform: rotate(-8deg); z-index: -1; }
.cs-craft__cta { margin-top: 30px; }
.cs-craft__shot { position: relative; aspect-ratio: 4 / 3; border-radius: 18px; overflow: hidden; background: #DDE2D5; }
/* Nudge this if heads sit badly in the crop; the source is 2500x1657. */
.cs-craft__img { object-fit: cover; object-position: center 30%; }

/* ── Proof ───────────────────────────────────────────────────────────── */
.cs-proof { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 60px; align-items: center; }
.cs-quote { font-family: var(--cs-display), sans-serif; font-weight: 700; font-size: clamp(25px, 3.2vw, 46px); line-height: 1.02; letter-spacing: -.03em; color: var(--ink); }
.cs-quote em { font-style: italic; }
.cs-attr { display: flex; align-items: center; gap: 14px; margin-top: 30px; }
.cs-attr__av { width: 46px; height: 46px; border-radius: 50%; background: var(--ink); display: flex; align-items: center; justify-content: center; font-family: var(--cs-display), sans-serif; font-weight: 800; color: var(--lime); font-size: 17px; }
.cs-attr__n { font-weight: 700; font-size: 16px; color: var(--ink); }
.cs-attr__r { font-size: 14px; color: #4a6b04; }
.cs-stat { background: var(--ink); border-radius: 20px; padding: 30px 28px; }
.cs-stat__v { font-family: var(--cs-display), sans-serif; font-weight: 800; font-size: 52px; letter-spacing: -.03em; color: #fff; line-height: 1; }
.cs-stat__l { font-size: 15px; color: rgba(255,255,255,0.7); margin-top: 8px; }

/* Two questions
   ------------------------------------------------------------------
   Two full width bands, alternating sides. Everything animated here runs on
   ONE loop length per band and is expressed as a percentage of it. Give the
   pieces their own durations and they drift out of step within a minute.
   ------------------------------------------------------------------ */
/* Narrower than the page cap. At 1600 the frame gets over 1100px and stops
   looking like a browser: the chrome stretches into a letterbox and every bar
   inside it turns into a rule across the screen. 1040 leaves the frame just
   under 600, which is close to a real browser window and stops the graphic
   dominating the sentence next to it.
   Written as .cs-sec > .cs-two so it beats the page-wide
   .cs-sec > *:not(.cs-secdeco) cap. Equal specificity, later in the file. */
.cs-sec > .cs-two { max-width: 1040px; }
.cs-band { display: grid; grid-template-columns: minmax(0, 1fr) 380px; gap: 64px; align-items: center; margin-top: 76px; }
.cs-band--flip { grid-template-columns: 380px minmax(0, 1fr); }
.cs-band--flip .cs-band__viz { order: 2; }
.cs-band__viz { position: relative; }
.cs-band__say { position: relative; }
/* z-index -1 puts these behind the frame, so the mock crops whatever reaches
   it and they read as tucked behind rather than pasted on. It only works
   because the section carries cs-sec--deco: without that stacking context, -1
   lands behind the SECTION background and the shape vanishes entirely. */
.cs-bdeco { position: absolute; z-index: -1; display: block; }
.cs-bdeco--slab { top: -64px; left: -78px; transform: scaleX(-1); }
.cs-bdeco--spark { top: -34px; right: -30px; z-index: 1; transform: rotate(-14deg) scaleX(-1); }
.cs-bdeco--leak { top: 40%; right: -66px; transform: rotate(10deg); }
.cs-bdeco--climb { top: 34%; left: -72px; transform: rotate(-12deg) scaleX(-1); }
/* ── THE SIGNAL IS THE MIDDLE OF THREE LAYERS ────────────────────────────
   In front of the browser frame, behind the white recommendation pill. That
   is a sandwich, so BOTH edges have to be declared — see .cs-band__rec.

   z-index auto was the original state and put it on top of everything,
   because .cs-band__say comes after .cs-band__viz in the DOM and nothing
   said otherwise. -1 was the obvious correction and overshot: it dropped
   below the frame as well as the pill, since the frame is ordinary in-flow
   content and negative layers paint before all of it.

   1 is the middle rung. It clears the frame, which has no index of its own,
   and the pill sits on 2 above it. Neither viz nor say sets a z-index, so
   both stay transparent to stacking and all three layers are ordered inside
   the same context — the one .cs-sec--deco opens. */
.cs-bdeco--sig { position: absolute; z-index: 1; left: 2px; top: 100%; margin-top: 34px; transform: rotate(-14deg); }
.cs-bdeco--sig--up { left: auto; right: 16%; top: auto; bottom: 100%; margin: 0 0 26px; transform: rotate(16deg); }
.cs-band__n { font-family: var(--cs-mono), monospace; font-size: 12px; letter-spacing: .16em; color: rgba(255,255,255,0.35); margin-bottom: 14px; }
.cs-band__h { font-family: var(--cs-display), sans-serif; font-weight: 700; font-size: 34px; letter-spacing: -.025em; color: #fff; margin-bottom: 12px; }
.cs-band__b { font-size: 17px; line-height: 1.6; color: rgba(255,255,255,0.62); }

/* The recommendation card deliberately breaks the frame edge. A panel that
   hands you something reads differently from a panel that displays something,
   and crossing a boundary is the cheapest way to say so.
   PAPER, NOT LIME, and the reason is meaning rather than taste. Lime already
   marks what CSignal found: the scan line, the lit rank row, the mention chip.
   A lime recommendation card made one colour carry two jobs. Lime is now
   what we saw, paper is what to do about it. Keep that split. */
@keyframes csrec {
  0%, 34% { opacity: 0; transform: translate(12px, 12px) }
  46%, 80% { opacity: 1; transform: translate(0, 0) }
  92%, 100% { opacity: 0; transform: translate(12px, 12px) }
}
/* z-index 2 is the TOP of the three-layer sandwich described on
   .cs-bdeco--sig: frame, then the Signal shape, then this. It reads as
   redundant on desktop, where DOM order happens to give the same result,
   and it is not — the shape sits in the other column, so the only thing
   keeping the pill above it is this number. */
.cs-band__rec { position: absolute; z-index: 2; right: -24px; bottom: -26px; width: 244px; background: var(--paper); color: var(--ink); border-radius: 14px; padding: 14px 16px; font-size: 14px; line-height: 1.4; font-weight: 600; animation: csrec 8s cubic-bezier(.2,.9,.2,1) infinite; }
/* Positive delay, not negative. Negative means the animation is already that
   far in, so it would land EARLIER and the two cards would arrive almost
   together. This one waits, and because the periods are 8s and 14s they also
   drift rather than locking into a rhythm. */
.cs-band__rec--l { right: auto; left: -26px; animation-duration: 14s; animation-delay: 2s; }
.cs-band__reck { display: block; font-family: var(--cs-mono), monospace; font-weight: 600; font-size: 10px; letter-spacing: .16em; text-transform: uppercase; color: #6B7A70; margin-bottom: 5px; }

/* Browser chrome, shared by both bands. */
.cs-fr { border: 1px solid rgba(255,255,255,0.16); border-radius: 14px; overflow: hidden; }
.cs-fr__bar { background: rgba(255,255,255,0.07); padding: 11px 14px; display: flex; align-items: center; gap: 7px; }
.cs-fr__bar i { width: 9px; height: 9px; border-radius: 50%; background: rgba(255,255,255,0.2); }
.cs-fr__bar b { flex: 1; height: 15px; border-radius: 5px; background: rgba(255,255,255,0.09); margin-left: 8px; }
.cs-fr__bd { background: #132720; }

/* 01 · the store page. Silhouette only, no words: the point is that it is
   recognisably a shop, not that it is any particular shop. */
@keyframes csring {
  0%, 14% { opacity: 0; transform: scale(.94) }
  24%, 66% { opacity: 1; transform: scale(1) }
  78%, 100% { opacity: 0; transform: scale(.94) }
}
@keyframes csscan {
  0%, 6% { opacity: 0; top: 12% }
  16% { opacity: 1 }
  44% { opacity: 1; top: 78% }
  54%, 100% { opacity: 0; top: 78% }
}
.cs-shop { position: relative; padding: 18px; }
.cs-shop__hero { display: block; height: 66px; border-radius: 9px; background: rgba(255,255,255,0.09); margin-bottom: 14px; }
.cs-shop__row { display: flex; gap: 13px; margin-bottom: 14px; }
.cs-shop__t { flex: 1; position: relative; height: 84px; border-radius: 9px; background: rgba(255,255,255,0.07); }
.cs-shop__ring { position: absolute; inset: -6px; border: 2px solid var(--coral); border-radius: 13px; animation: csring 8s ease-in-out infinite; }
.cs-shop__line { display: block; height: 28px; width: 58%; border-radius: 9px; background: rgba(255,255,255,0.07); }
.cs-shop__scan { position: absolute; left: 0; right: 0; height: 2px; background: var(--lime); animation: csscan 8s ease-in-out infinite; }

/* 03 · orders stack, value compounds. Same 8s loop as the store scan so the
   three band graphics stay in step. Blocks build bottom up, left to right,
   and each column's money lands the moment its last block does. */
@keyframes matblk { 0% { opacity: 0; transform: translateY(9px) scaleY(.3) } 8%, 76% { opacity: 1; transform: none } 92%, 100% { opacity: 0; transform: translateY(9px) scaleY(.3) } }
@keyframes matval { 0% { opacity: 0 } 8%, 76% { opacity: 1 } 92%, 100% { opacity: 0 } }
.cs-mat { padding: 18px 20px 20px; }
.cs-mat__cols { display: flex; align-items: flex-end; gap: 14px; margin-top: 20px; min-height: 152px; }
.cs-mat__c { flex: 1; min-width: 0; }
.cs-mat__stack { display: flex; flex-direction: column-reverse; gap: 5px; }
.cs-mat__blk { display: block; height: 13px; border-radius: 3px; background: rgba(255,255,255,0.16); animation: matblk 8s ease-out infinite both; }
.cs-mat__c.is-lit .cs-mat__blk { background: var(--lime); }
.cs-mat__v { font-family: var(--cs-mono), monospace; font-size: 14px; color: rgba(255,255,255,0.55); margin-top: 12px; animation: matval 8s ease-out infinite both; }
.cs-mat__c.is-lit .cs-mat__v { color: var(--lime); }
.cs-mat__k { font-family: var(--cs-mono), monospace; font-size: 9.5px; letter-spacing: .12em; text-transform: uppercase; color: rgba(255,255,255,0.3); margin-top: 6px; }
.cs-mat__c.is-lit .cs-mat__k { color: rgba(201,247,59,0.75); }

/* 02 · one window, two scenes, cross fading on a single 14s loop. */
@keyframes cssca { 0% { opacity: 0 } 3%, 44% { opacity: 1 } 49%, 100% { opacity: 0 } }
@keyframes csscb { 0%, 49% { opacity: 0 } 55%, 94% { opacity: 1 } 99%, 100% { opacity: 0 } }
@keyframes csty { 0%, 4% { width: 0 } 15%, 100% { width: var(--w) } }
@keyframes cschip { 0%, 16% { opacity: 0; transform: scale(.86) } 21%, 100% { opacity: 1; transform: scale(1) } }
@keyframes cssrcup { 0%, 26% { transform: translateY(0) } 36%, 100% { transform: translateY(-52px) } }
@keyframes cssrclit { 0%, 26% { background: rgba(255,255,255,0.07) } 36%, 100% { background: var(--lime) } }
@keyframes csgup { 0%, 68% { transform: translateY(0) } 80%, 100% { transform: translateY(-78px) } }
@keyframes csglit { 0%, 68% { background: rgba(255,255,255,0.07) } 80%, 100% { background: var(--lime) } }
.cs-scenes { position: relative; height: 268px; }
.cs-sc { position: absolute; inset: 0; padding: 18px 20px; }
.cs-sc--a { animation: cssca 14s linear infinite; }
.cs-sc--b { animation: csscb 14s linear infinite; }
.cs-lbl { font-family: var(--cs-mono), monospace; font-size: 10px; letter-spacing: .16em; text-transform: uppercase; color: rgba(255,255,255,0.4); margin-bottom: 14px; }
.cs-lbl--gap { margin-top: 20px; }
.cs-ty { display: block; width: var(--w); height: 10px; border-radius: 5px; background: rgba(255,255,255,0.13); margin-bottom: 9px; animation: csty 14s ease-out infinite; }
.cs-ansrow { display: flex; align-items: center; gap: 9px; margin-bottom: 9px; }
.cs-ansrow .cs-ty { margin-bottom: 0; }
.cs-chip { font-family: var(--cs-body), sans-serif; font-style: normal; font-size: 12px; font-weight: 600; background: var(--lime); color: var(--ink); border-radius: 5px; padding: 2px 8px; animation: cschip 14s ease-out infinite; }
.cs-list { overflow: hidden; }
.cs-list--src { height: 68px; }
.cs-list--g { height: 194px; }
.cs-list__in { display: flex; flex-direction: column; gap: 9px; }
.cs-list__in--src { animation: cssrcup 14s cubic-bezier(.3,.8,.2,1) infinite; }
.cs-list__in--g { animation: csgup 14s cubic-bezier(.3,.8,.2,1) infinite; }
.cs-row { display: flex; align-items: center; gap: 12px; }
.cs-row__n { font-family: var(--cs-mono), monospace; font-size: 11px; color: rgba(255,255,255,0.4); width: 12px; flex: none; }
.cs-row__n.is-lit { color: var(--lime); }
.cs-row__b { flex: 1; height: 17px; border-radius: 6px; background: rgba(255,255,255,0.07); }
.cs-row__b.is-src { animation: cssrclit 14s ease-out infinite; }
.cs-row__b.is-g { animation: csglit 14s ease-out infinite; }

@media (prefers-reduced-motion: reduce) {
  .cs-band__rec, .cs-shop__ring, .cs-shop__scan, .cs-sc, .cs-ty, .cs-chip,
  .cs-list__in, .cs-row__b { animation: none }
  .cs-sc--b { opacity: 0 }
}

@media (max-width: 1100px) {
  .cs-band, .cs-band--flip { grid-template-columns: minmax(0, 1fr); gap: 40px; }
  .cs-band--flip .cs-band__viz { order: 0; }
  .cs-band__rec, .cs-band__rec--l { right: 12px; left: auto; bottom: -22px; }
}

/* ── Footer ──────────────────────────────────────────────────────────── */
.cs-foot { background: var(--ink); padding: 70px 60px 40px; }
.cs-foot > * { max-width: var(--cs-max); margin-inline: auto; }
.cs-foot__in { display: grid; grid-template-columns: 1.4fr 1fr 1fr 1fr; gap: 40px; padding-bottom: 48px; border-bottom: 1px solid rgba(255,255,255,0.12); }
.cs-foot__by { font-size: 15px; line-height: 1.55; color: rgba(255,255,255,0.55); margin-top: 20px; max-width: 320px; }
.cs-foot__h { font-family: var(--cs-mono), monospace; font-size: 11px; letter-spacing: .16em; text-transform: uppercase; color: rgba(255,255,255,0.4); margin-bottom: 14px; }
.cs-foot__l { display: block; font-size: 15px; color: rgba(255,255,255,0.72); padding: 5px 0; }
.cs-foot__l:hover { color: var(--lime); }
.cs-foot__end { display: flex; justify-content: space-between; gap: 20px; flex-wrap: wrap; padding-top: 26px; font-family: var(--cs-mono), monospace; font-size: 11.5px; color: rgba(255,255,255,0.4); }

/* ── Narrow ──────────────────────────────────────────────────────────────
 *
 * ── EVERY SINGLE-COLUMN RULE IS minmax(0, 1fr), NEVER 1fr ────────────────
 *
 * This was the bug behind the whole broken mobile hero, and it is worth
 * understanding rather than pattern-matching, because it looks like nothing.
 *
 * A bare 1fr is minmax(AUTO, 1fr), and that auto floor is the track's
 * MIN-CONTENT width — the narrowest the contents can get without overflowing
 * themselves. The hero deck's cards are full of nowrap rows, so their
 * min-content is somewhere near 480px. On a 375px phone the single column
 * therefore sized itself to 480px and everything in it was laid out to that
 * width: the lede wrapped at 480 and ran off the screen, the nowrap headline
 * was sliced, and the cards hung past the right edge. It read as five
 * separate bugs and it was one.
 *
 * minmax(0, 1fr) sets that floor to zero, so the track takes the width it is
 * actually given and the contents deal with it. Any new single-column rule
 * added below needs the same treatment.
 */
/* ── THE LAST 80px OF THE NAV ────────────────────────────────────────────
 * The links can shrink their BOX but not their contents — every item is
 * nowrap, by design — so once the row runs out of room the overflow lands on
 * top of the actions and "Charle" prints through "Log in". Clipping it would
 * hide a link and letting it push would put a scrollbar on the page, so the
 * only honest fix is to give the row the width back before it gets there.
 *
 * The attribution is what goes: rule plus gap plus the 54px Charle wordmark
 * is about 83px, it is the most expendable thing in the bar, and it was
 * already being dropped 200px further down. Losing it here means the full
 * link set survives to 1160 instead of colliding at 1280.
 */
@media (max-width: 1280px) {
  .cs-nav__rule, .cs-nav__by { display: none; }
  .cs-nav__login { padding-left: 12px; padding-right: 12px; }
}
/* Below this the links genuinely will not fit at any type size worth
   reading, so they stop being links in a bar and become a menu. Kept in step
   with the matchMedia query in mobile-menu.tsx — if one moves, both move. */
@media (max-width: 1160px) {
  .cs-nav__links { display: none; }
  /* space-between was distributing three items across the bar, which parked
     the actions in the middle once the links were hidden. The auto margin
     absorbs the free space instead, so the actions and the burger sit
     together on the right with only the nav gap between them. */
  .cs-nav__acts { margin-left: auto; }
  /* The links have just gone; this is what replaces them. */
  .cs-burger { display: inline-flex; }
}
@media (max-width: 1080px) {
  .cs-hero__in, .cs-proof { grid-template-columns: minmax(0, 1fr); }
  .cs-g3, .cs-g4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 900px) {
  /* ── BODY COPY, ONE STEP DOWN ──
     Everything here was sized for a 1600px page and carried straight down to
     a phone, where 17px over a 330px measure is about six words a line. The
     display sizes are clamped at their own declarations; these are the flat
     ones that had no ramp at all. */
  .cs-card__b, .cs-band__b, .cs-craft__p, .cs-sechead__note { font-size: 15px; }
  .cs-card__h { font-size: 25px; }
  .cs-band__h { font-size: 28px; }
  .cs-stat__v { font-size: 42px; }
  .cs-stat__l, .cs-foot__by, .cs-foot__l { font-size: 14px; }
  .cs-eyebrow { font-size: 11px; margin-bottom: 14px; }

  /* ── HOW IT WORKS ──
     The 290px rail plus a 48px gap left the stage a negative share of a
     330px page, so its fr track resolved to zero and the panels were laid
     out inside a column with no width. Rail on top, stage underneath. */
  .cs-how { grid-template-columns: minmax(0, 1fr); gap: 34px; }

  /* ── AND THE PANELS THEMSELVES ──
     Absolute panels inside a 320px min-height box is a desktop-only bargain:
     it holds only while every panel fits 320, and absolutely positioned
     children contribute NO height, so the moment one is taller it spills out
     of the section instead of pushing it down. At a third of the width all
     five are taller.

     Stacking them in a single grid cell fixes it without touching the
     cross-fade — they still overlap exactly, they still transition on
     opacity, but the row now sizes to the tallest one and the section grows
     with it. Left absolute above 900, so desktop is untouched. */
  .cs-how__stage { display: grid; min-height: 0; }
  .cs-how__pan { position: relative; inset: auto; grid-area: 1 / 1; }
  /* Stacked rather than hidden, so the four dormant panels are still over
     the live one and would otherwise take its taps. */
  .cs-how__pan:not(.is-on) { pointer-events: none; }

  /* ── HUMAN CRAFT ──
     A fixed 1fr 1fr with a 60px gutter gave the copy and the 4:3 photograph
     about 135px each. Stacked, text first, photo under it. */
  .cs-craft { grid-template-columns: minmax(0, 1fr); gap: 34px; }

  /* ── THE SHAPE LAYER IS SIZED FOR A 1600px HERO ──
     Every offset here is measured from the right edge on the assumption that
     the deck column is over there and the copy is not. On one column that
     assumption is gone and the shapes land on top of the content: the 240px
     Leak in particular sat straight across "See how it works", which reads
     as a rendering fault rather than as decoration.

     Leak comes out. Store stays, smaller and pushed further off the edge,
     because it is the only one doing a structural job — it is the block that
     gives the fold its depth, and without it the hero is flat ink. Spike and
     Signal are already off the left of a narrow screen at these offsets, so
     they are switched off rather than left to be clipped for nothing. */
  .cs-deco--leak, .cs-deco--spike, .cs-deco--signal { display: none; }
  .cs-deco--store { top: 30px; right: -300px; }
  .cs-deco--store svg { height: 560px; }
  .cs-deco--ask { top: 40px; right: 20px; }
  .cs-deco--ask svg { height: 72px; }
}
@media (max-width: 720px) {
  .cs-nav, .cs-hero, .cs-sec, .cs-foot { padding-left: 22px; padding-right: 22px; }
  /* .cs-g2 is NOT in this list. The stat cards stay two across, because four
     of them stacked is a full screen of scrolling for four short facts, and
     a 2x2 lets the eye take them as one block — which is what a stat grid is
     for. Everything else here has a sentence in it and genuinely needs the
     width. */
  .cs-g3, .cs-g4, .cs-foot__in { grid-template-columns: minmax(0, 1fr); }
  .cs-g2 { gap: 12px; }
  /* The number has to fit a half-width column: "128,400" at 42px needed
     about 190px against the 155px it now gets. Sized to the longest value in
     STATS, so adding a longer one means revisiting this. */
  .cs-stat { padding: 20px 18px; }
  .cs-stat__v { font-size: 30px; }
  .cs-stat__l { font-size: 12.5px; line-height: 1.4; margin-top: 6px; }
  /* Bottom padding MATCHES the hero grid gap, and that pairing is the whole
     point. The gap is the space above the deck and this is the space below
     it, so the card only reads as centred between the CTAs and the marquee
     if the two are the same number. Change one and change the other. */
  .cs-hero { padding-top: 40px; padding-bottom: 34px; }
  .cs-sec { padding-top: 64px; padding-bottom: 68px; }

  /* HARD-CODED LINE BREAKS ARE A DESKTOP DECISION. The h2s are written with
     br tags because at 68px the designer picks the rag by hand. At 28px on a
     330px column those same breaks fight the natural wrap and produce a
     ragged four-line stack with half the measure empty — "Most agencies
     report / on last month. You're / watching right now." became six lines,
     three of them short. Dropping the breaks lets the column set the rag. */
  .cs-h2 br { display: none; }

  /* The bar keeps the wordmark and the burst only. Log in and the CTA are
     both in the panel, where the CTA gets full width rather than competing
     with a 168px logo for a 330px row. */
  .cs-nav__acts { display: none; }
  .cs-burger { margin-left: auto; }
  .cs-nav__logo svg { width: 132px; height: auto; }

  /* Sized down with the section, and the mark with it — it is declared at 34
     to match two lines of 13px type, so it has to move when the type does. */
  .cs-kicker { font-size: 11px; gap: 10px; padding: 9px 13px; border-radius: 12px; box-shadow: 0 0 0 4px #fff, 0 8px 18px rgba(14, 28, 22, 0.18); }
  .cs-kicker__mark { height: 26px; width: auto; }

  /* ── HERO DECK CARDS ──
     The card heights are enforced in JS from the design width, and the
     contents are set for it too: at 330px the footer's number, Dismiss and
     Apply tweak needed about 360px on one nowrap row and the last button was
     cut off by the card's own overflow. Everything below buys that row back
     rather than letting it wrap, because a wrapped row would grow past a
     height the card cannot change. */
  .cs-reco, .cs-geo { padding: 18px 18px; border-radius: 18px; }
  .cs-reco__h { font-size: 20px; }
  .cs-reco__b { font-size: 13px; }
  .cs-reco__big { font-size: 27px; }
  .cs-reco__foot { gap: 10px; padding-top: 14px; }
  .cs-reco__btns { gap: 6px; }
  .cs-reco__foot .cs-btn--sm { font-size: 12px; padding: 9px 13px; }
  .cs-geo__h { font-size: 30px; }

  /* ── THE DECK IS SUPPORT, NOT THE SUBJECT ──
     Two cards is about 590px of a 390px screen: taller than the headline,
     the lede and both CTAs together, and arriving directly under them. The
     fold stops being a hero the moment the proof outweighs the promise.

     Scaling it was the wrong instinct and is now gone — at .78 it was a
     smaller object that still dominated, and the card type went with it for
     nothing. The deck shows ONE card here instead of two, at full size, so
     the loop is intact and the footprint halves. --deck-h1 is the tallest
     single card, sent from the component alongside the two-card figure.

     The hero gap comes in with it. 56px was a gutter between two columns;
     stacked, it is just a hole between the CTAs and the card. */
  .cs-deck {
    height: calc(var(--deck-h1) * var(--deck-scale));
    /* ── THE BLEED IS BACK, AND ONLY THE TIMING MAKES IT SAFE ──
       Zeroing these stopped the overspill but hard-cut the cards at the
       window edge, so they appeared and vanished instead of flying. What
       made the bleed unsafe was never its size — it was that the card one
       pitch below stayed painted for a second beat. cs-deck-card-one gives
       each card a single beat, so its neighbours are transparent whenever
       it is on screen and the bleed has nothing to reveal.

       120 down is measured, not picked: it clears the hero's bottom padding
       with room over, so the entry starts past the section edge and the card
       rises from behind the marquee rather than out of thin air.
       90 up lets the exit clear the window before it fades. */
    --bleed-t: 90px;
    --bleed-b: 120px;
  }
  /* ── THE SHARED HEIGHT IS A SLOT, NOT A CARD ──
     One height makes the pitch constant, which is what lets one window hold
     exactly one card. But the card must not BE that height: the desktop rule
     stretches .cs-deck__card > * to fill its slot and centres the contents,
     so a 176px card in a 252px slot came out with 38px of dead space above
     and below it and read as a padding bug.

     So the slot keeps the height and the card inside it goes back to sizing
     itself, top-aligned — every card then lands with its top edge on the
     same line, which is the one thing that should not vary.

     This also retires the risk in CARD_H_ONE. The number is now purely a
     pitch: nothing is enforced against a card any more, so a card whose
     content outgrows it simply reaches a little further into the bleed
     instead of clipping itself.

     CENTRED in the slot, not top-aligned. Top-aligning put every card's top
     edge on the same line, which was the tidier idea, but it also dropped
     the slot's entire slack underneath the card — so a short card left a
     visible hole above the marquee and the fold looked bottom-heavy. Split
     evenly it reads as air around the card instead of a gap under it.
     The cost is that a card's top edge now moves a little from one to the
     next; against cards that fly in and out anyway, that is the cheaper of
     the two. */
  .cs-deck__card {
    height: var(--deck-h1);
    animation-name: cs-deck-card-one;
    animation-delay: var(--d1);
    display: flex;
    flex-direction: column;
    justify-content: center;
  }
  /* Column direction matters: it puts justify-content on the VERTICAL axis
     so the centring works, and leaves the horizontal as the cross axis where
     stretch is the default — a row here would shrink the card to the width
     of its own text. */
  .cs-deck__card > * { height: auto; }
  .cs-track { animation-name: cs-track-one; }
  .cs-deckdeco { display: block; }
  .cs-hero__in { gap: 34px; }

  /* The rail's own furniture. The dots hang 30px into a left pad that is
     most of a phone's gutter, and five steps at 19px was a screen of its
     own before the panel it controls. */
  .cs-how { gap: 26px; }
  .cs-how__rail { padding-left: 26px; }
  .cs-how__track { left: 3px; }
  .cs-how__step { padding: 10px 0; }
  .cs-how__step em { left: -26px; top: 17px; }
  .cs-how__step h3 { font-size: 17px; }
  .cs-how__kick { grid-template-columns: minmax(0, 1fr); gap: 18px; }
  .cs-how__card, .cs-how__rechd, .cs-how__recbd { padding: 18px; }
  .cs-how__rect { font-size: 19px; }
  .cs-how__h, .cs-how__flighth { font-size: 18px; }

  /* ── SHAPES THAT HANG OFF THE RIGHT OF SOMETHING ──
     Both are pinned at left:100% of an element that used to be half the
     grid and is now the whole column, so they land outside the section and
     its overflow:clip eats them. A shape that is always invisible is just
     paint, so it comes out rather than being nudged. */
  .cs-craft__mark, .cs-craft__trail, .cs-crd--ask { display: none; }
}

@keyframes cs-track {
  0.000% { transform: translateY(-0px); }
  11.400% { transform: translateY(-0px); }
  16.667% { transform: translateY(-300px); }
  28.067% { transform: translateY(-300px); }
  33.333% { transform: translateY(-490px); }
  44.733% { transform: translateY(-490px); }
  50.000% { transform: translateY(-790px); }
  61.400% { transform: translateY(-790px); }
  66.667% { transform: translateY(-1018px); }
  78.067% { transform: translateY(-1018px); }
  83.333% { transform: translateY(-1208px); }
  94.733% { transform: translateY(-1208px); }
  100% { transform: translateY(-1436px); }
}
@keyframes cs-track-one {
  0.000% { transform: translateY(-0px); }
  11.400% { transform: translateY(-0px); }
  16.667% { transform: translateY(-266px); }
  28.067% { transform: translateY(-266px); }
  33.333% { transform: translateY(-532px); }
  44.733% { transform: translateY(-532px); }
  50.000% { transform: translateY(-798px); }
  61.400% { transform: translateY(-798px); }
  66.667% { transform: translateY(-1064px); }
  78.067% { transform: translateY(-1064px); }
  83.333% { transform: translateY(-1330px); }
  94.733% { transform: translateY(-1330px); }
  100% { transform: translateY(-1596px); }
}
@keyframes cs-deck-card-one {
  0%     { transform: translate(52%, 110px) rotate(7deg); opacity: 0; }
  2.5%   { opacity: 1; }
  5.267%  { transform: translate(0, 0) rotate(0deg); opacity: 1; }
  16.667% { transform: translate(0, 0) rotate(0deg); opacity: 1; }
  21%    { transform: translate(-44%, -70px) rotate(-9deg); opacity: 0; }
  100%   { transform: translate(-44%, -70px) rotate(-9deg); opacity: 0; }
}