/* ===== 00-tokens.css ===== */
/*
 * Link Prism design tokens.
 *
 * Derived from the Steep style reference (docs: DESIGN.md) with two corrections
 * made after measuring the real site, both of which the written reference omits
 * and both of which are what actually make it read as premium:
 *   1. The hero is NOT flat white — it sits on a soft chromatic gradient wash.
 *   2. A fractal-noise grain overlays the whole page at low opacity.
 * Everything else here matches the reference's documented scale.
 */
:root {
  /* — Ink & paper —————————————————————————————— */
  --ink: #17191c;
  /*
   * Paper is NOT pure white, deliberately. Two reasons, both measured:
   *  - `overlay` grain over #ffffff is an identity operation (overlay(1,x) === 1),
   *    so a pure-white page can carry no texture at all.
   *  - The reference's "white" is never actually white; every band sits on a
   *    faint warm ground, which is most of why it reads as paper and not screen.
   */
  --paper: #fdfcfa;
  --raised: #ffffff;        /* floating artifacts stay true white so they lift off the paper */
  --mist: #f2f2f3;
  --fog: #fafafb;
  /*
   * These three were taken from the reference palette and all FAILED WCAG AA on
   * our paper: slate 4.13:1, ash 2.84:1, smoke 2.37:1 against 4.5:1 for small
   * text. Every eyebrow, footnote and body paragraph on the site was below the
   * threshold. Darkened within the same family until they pass; the greys read
   * fractionally deeper and everything stays legible.
   */
  --slate: #636772;   /* 4.52:1 worst ground (peach) — body copy, ledes, muted text */
  --ash:   #686c76;   /* 4.70:1 worst ground (mist) — eyebrows, footnotes, labels  */
  --smoke: #7d818b;   /* 3.80:1 — LARGE type only (>=24px), never small text */

  /*
   * TEXT ON DARK SURFACES. The greys above are light-ground tokens; several
   * sections place a dark ink panel on the page and reused them inside it, where
   * mid-grey on near-black measures 3.4:1. These are the dark-surface equivalents
   * and are the ONLY muted colours permitted inside an ink panel.
   */
  --on-dark:        color-mix(in srgb, var(--paper) 92%, transparent);  /* body on ink   */
  --on-dark-muted:  color-mix(in srgb, var(--paper) 66%, transparent);  /* labels, ~7:1  */
  --on-dark-faint:  color-mix(in srgb, var(--paper) 48%, transparent);  /* LARGE type only */
  --peach: #fbe1d1;
  --sienna: #5d2a1a;
  --hair: #ececec;
  --hair-strong: rgba(23, 25, 28, 0.12);

  /* Warm tints for the gradient wash — same family as --peach, never used as text */
  --wash-1: #ffd9c2;
  --wash-2: #ffe9d6;
  --wash-3: #f0e2f2;
  --wash-4: #dfe6f5;

  /* — Type —————————————————————————————————————— */
  --serif: 'Source Serif 4', 'Iowan Old Style', Georgia, ui-serif, serif;
  --sans: 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;

  --t-caption: 15px;
  --t-body: 17px;
  --t-body-lg: 20px;
  --t-subhead: 22px;
  --t-h-sm: 26px;
  --t-h: 44px;
  --t-h-lg: 64px;
  --t-display: 90px;

  /* Tracking tightens as size grows — the typographic signature of the system */
  --tr-display: -0.025em;
  --tr-h-lg: -0.015em;
  --tr-h: -0.015em;
  --tr-h-sm: -0.009em;
  --tr-body-lg: -0.009em;

  /* — Space (4px base) ————————————————————————— */
  --s4: 4px;   --s8: 8px;   --s12: 12px; --s16: 16px; --s20: 20px;
  --s24: 24px; --s28: 28px; --s32: 32px; --s40: 40px; --s48: 48px;
  --s64: 64px; --s80: 80px; --s96: 96px; --s128: 128px; --s160: 160px;

  --nav-h: 76px;
  --page: 1200px;
  --gutter: 32px;

  /*
   * THREE SECTION RHYTHMS, AND ONLY THREE.
   *
   * Nineteen section files used to set their own band height, and they split
   * into two systems: multipliers of --section-y (0.66, 0.82, 1.2, 1.7 …) and
   * fixed pixels (104, 112, 136, 176 …). The two agree at exactly one viewport
   * width and separate everywhere else, so the page's vertical beat changed
   * with the window. Every band now picks one of these and nothing derives its
   * own.
   */
  --section-y-tight: 64px;   /* a strip: a logo rail, a short ledger */
  --section-y: 96px;         /* the normal band — most of the site */
  --section-y-loose: 128px;  /* a stage: the closing band, a tall drawing */

  /*
   * ONE HERO OPENING for all eight pages. The nav is sticky and 76px tall and
   * every hero is pulled up under it, so this figure INCLUDES the nav band:
   * 148 is 72px of visible air above the first mark, on every page. It used to
   * run 148 / 172 / 228 / 244 / 252 across eight heroes that share one nav.
   */
  --hero-y: 148px;

  /* One eyebrow, one gap under it. Seventeen instances used to set thirteen. */
  --eyebrow-gap: var(--s24);

  /* — Shape ——————————————————————————————————— */
  --r-card: 24px;
  --r-elevated: 20px;
  --r-input: 16px;
  --r-small: 16px;
  --r-pill: 9999px;

  /* — Drawings ————————————————————————————————
   * TWO sizes for the whole site, and no third. A set of drawings at six
   * different sizes is size used as hierarchy, which _src/ILLUSTRATION.md
   * items 3 and 7 forbid: a terminal and a printer are comparable objects and
   * must be drawn comparably. `card` is the mark that sits inside a card in a
   * grid; `feature` is the one drawing a section is built around. Both are
   * CEILINGS — `.illo--card` / `.illo--feature` pair them with width:100%, so a
   * narrow column still fits its drawing without inventing a third size.
   */
  --illo-card: 250px;
  --illo-feature: 500px;

  /* Only floating product artifacts earn elevation. Content cards stay flat. */
  --sh-artifact: 0 0 0 1px rgba(4, 23, 43, 0.05), 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
  --sh-overlay: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 8px 40px 0 rgba(0, 0, 0, 0.1);
  --sh-pop: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 4px 24px 0 rgba(0, 0, 0, 0.08);

  /* — Motion ——————————————————————————————————— */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --dur-fast: 160ms;
  --dur: 320ms;
  --dur-slow: 720ms;
}

/* The three rhythms and the hero opening ramp together, so the ratios between
   them hold at every width and no section has to re-tune itself. */
@media (max-width: 1080px) {
  :root { --t-display: 68px; --t-h-lg: 48px; --t-h: 36px; --gutter: 28px;
          --section-y-tight: 56px; --section-y: 80px; --section-y-loose: 104px;
          --hero-y: 124px; }
}
@media (max-width: 720px) {
  :root { --t-display: 44px; --t-h-lg: 34px; --t-h: 28px; --t-h-sm: 22px;
          --t-body-lg: 18px; --gutter: 20px; --r-card: 20px;
          --section-y-tight: 40px; --section-y: 64px; --section-y-loose: 80px;
          --hero-y: 104px; }
}

/* ===== 10-base.css ===== */
/* Reset + page substrate: the grain and the gradient wash live here. */
*, *::before, *::after { box-sizing: border-box; }
/*
 * `overflow-x: clip` on the ROOT, not `hidden` on body alone.
 * Sections intentionally crop product cards past the viewport edge; with only
 * body clipping, the document still grew (measured 1510px against a 1440px
 * viewport) and shipped a horizontal scrollbar. `clip` contains it WITHOUT
 * creating a scroll container, so the sticky nav keeps working — `hidden` would
 * silently break it.
 */
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; overflow-x: clip; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--t-body);
  line-height: 1.35;
  font-weight: 400;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: clip;
}

h1, h2, h3, h4, p, figure, blockquote { margin: 0; }
img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; }

/*
 * THE GRAIN. A fractal-noise tile at low opacity in `overlay` blend, fixed to the
 * viewport so it never scrolls against the content. This is the single largest
 * contributor to the reference's "printed paper" quality and it is absent from
 * every written description of the system.
 */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 9999;
  pointer-events: none;
  /*
   * `multiply`, not `overlay`. The noise tile is opaque-black-or-transparent, and
   * overlay leaves any near-white base untouched — the grain was mathematically
   * invisible across every light section. Multiply darkens the speckle instead,
   * so the texture reads on paper AND on the warm wash.
   */
  opacity: 0.13;
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.91' numOctaves='2' seed='7' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='discrete' tableValues='0 1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 180px 180px;
}
@media (prefers-reduced-motion: no-preference) { body::after { will-change: opacity; } }

/* Layout primitives.
   ONE page rail, and every headline block on the site sits on it. There used to
   be a second, wider container (`.wrap-wide`, 1400) used by three sections; at
   1440 it put their headlines on the 32px page gutter while their neighbours
   held 120, an 88px jump between consecutive sections. Full bleed is still
   legal — a drawing, a rule, a background band — but it is done per element,
   never by moving a headline off the rail. */
.wrap { width: min(var(--page), 100% - var(--gutter) * 2); margin-inline: auto; }
.section { padding-block: var(--section-y); position: relative; }
.section--fog { background: var(--fog); }
.section--mist { background: var(--mist); }

/* Type primitives */
.display, .h-lg, .h, .h-sm-serif {
  font-family: var(--serif);
  font-weight: 400;
  font-optical-sizing: auto;
  text-wrap: balance;
  /*
   * THE SIDEBEARING CORRECTION, ONCE, FOR THE WHOLE SITE.
   *
   * A serif cap set flush to a rail reads as indented: the stem sits inside the
   * glyph's left sidebearing, while the sans eyebrow above it and the sans lede
   * below it start on their own ink. Pulling the block back a hair of its own
   * size puts the STEM on the rail, and stated in em it re-derives itself at
   * every step of the type ramp instead of needing a value per breakpoint.
   * Measured on Source Serif 4: cap B, lowercase b and the italic a all land
   * within 1px of each other at -0.026em.
   *
   * This lived in hardware-hero.css as a private rule, bundled with a 64px
   * "hang" that pulled that one title to x=54 while its own eyebrow held 120.
   * The hang is gone; the correction is shared. Three more sections carried
   * near-duplicates of it (-0.02, -0.023, -0.03) and now inherit this one.
   *
   * A CENTRED serif block must switch it off — see .prh-title in pricing-hero —
   * because there the margin moves the centre rather than the left edge.
   */
  margin-left: -0.026em;
}
.display { font-size: var(--t-display); line-height: 1.06; letter-spacing: var(--tr-display); }
.h-lg    { font-size: var(--t-h-lg);   line-height: 1.1;  letter-spacing: var(--tr-h-lg); }
.h       { font-size: var(--t-h);      line-height: 1.16; letter-spacing: var(--tr-h); }
.h-sm-serif { font-size: var(--t-h-sm); line-height: 1.28; letter-spacing: var(--tr-h-sm); }
.display em, .h-lg em, .h em { font-style: italic; }

/*
 * THE ONE FIT-TO-WIDTH OPT-IN, AND THE ONLY ONE.
 *
 * A headline whose authored line genuinely cannot hold the phone column takes
 * this class on top of its scale class instead of inventing a divisor of its
 * own. Six inner heroes each invented one, and at 390px they rendered the same
 * rank at 31.8 / 41 / 41.6 / 42 / 44 and 48px — a 1.8x spread across six
 * elements that are the same thing.
 *
 * It can only ever make type SMALLER than the ramp: it is a min() against the
 * token, so it cannot be used to promote a headline past the class it wears.
 * The divisor is taken from the widest binding line on the site — "When
 * something breaks," which Source Serif sets at 10.47em once the optical axis
 * has opened the face up at small sizes — so one number covers every candidate.
 * Above about 414px of viewport it stops binding entirely and the token ramp is
 * the only thing sizing the line.
 */
.h-fit { font-size: min(var(--t-h-lg), calc((100vw - var(--gutter) * 2) / 11)); }

.h-sm { font-family: var(--sans); font-size: var(--t-h-sm); font-weight: 450;
        line-height: 1.22; letter-spacing: var(--tr-h-sm); }
.lede { font-size: var(--t-body-lg); line-height: 1.45; letter-spacing: var(--tr-body-lg);
        color: var(--slate); text-wrap: pretty; }
.body { font-size: 16px; line-height: 1.55; color: var(--slate); text-wrap: pretty; }
.caption { font-size: var(--t-caption); line-height: 1.5; color: var(--slate); }

/*
 * Ghost typographic tag — groups without visual weight. Never a badge.
 *
 * ONE COMPONENT: one size, one weight, one colour, and one gap to whatever it
 * labels. The gap used to take thirteen values across seventeen instances (2,
 * 16, 19, 24, 26, 28, 32, 38, 42, 45, 57 …) — even the four page-label eyebrows
 * that share the same hero pattern disagreed — and the colour ran --ash on 29
 * of them, --smoke on four, and full --ink at weight 500 on one.
 *
 * Where an eyebrow is a grid or flex participant whose spacing the container
 * already owns, it sets `margin-bottom: 0` and says so. That is the only legal
 * override.
 */
.eyebrow {
  font-size: 14px;
  font-weight: 400;
  color: var(--ash);
  letter-spacing: 0;
  margin-bottom: var(--eyebrow-gap);
}

.tnum { font-variant-numeric: tabular-nums; }
.muted { color: var(--slate); }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
:where(a, button, input, [tabindex]):focus-visible {
  outline: 2px solid var(--ink); outline-offset: 3px; border-radius: 4px;
}

/*
 * A hero that carries a wash starts UNDER the transparent nav, so the wash paints
 * behind the logo and links instead of leaving a bald white strip across the top.
 *
 * THE OPENING IS SET HERE AND NOWHERE ELSE. Eight heroes used to compute their
 * own top padding off --section-y plus a private number — 148 / 172 / 228 / 244
 * / 252, a 104px spread on eight components that share one 76px nav — and eight
 * more numbers for the bottom (96 … 236). --hero-y includes the nav band, so
 * every page shows the same 72px of air above its first mark, and the hero
 * closes on the loose rhythm like any other stage.
 */
main > .section:first-child.has-wash {
  margin-top: calc(var(--nav-h) * -1);
  padding-top: var(--hero-y);
  padding-bottom: var(--section-y-loose);
}

/* A whisper of warmth for sections that would otherwise be an undifferentiated slab. */
.section--tint { background:
  radial-gradient(120% 80% at 50% 0%, rgba(251, 225, 209, 0.30) 0%, rgba(251, 225, 209, 0) 60%),
  var(--paper); }
.section--tint-b { background:
  radial-gradient(120% 80% at 50% 100%, rgba(251, 225, 209, 0.26) 0%, rgba(251, 225, 209, 0) 60%),
  var(--paper); }

/* ===== 20-components.css ===== */
/* ————————————————————————————————————————————————
   The gradient wash. Original composition in the brand's warm family — four
   soft radial blooms over paper. Sections opt in with .has-wash.
   ———————————————————————————————————————————————— */
.has-wash { position: relative; isolation: isolate; }
.has-wash > .wash {
  position: absolute; inset: -10% -5% 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(58% 44% at 74% 30%, var(--wash-1) 0%, rgba(255, 217, 194, 0) 62%),
    radial-gradient(46% 38% at 22% 46%, var(--wash-3) 0%, rgba(240, 226, 242, 0) 66%),
    radial-gradient(52% 40% at 46% 72%, var(--wash-2) 0%, rgba(255, 233, 214, 0) 68%),
    radial-gradient(40% 34% at 90% 74%, var(--wash-4) 0%, rgba(223, 230, 245, 0) 70%);
  filter: blur(8px) saturate(112%);
}
/* Fade the wash into paper at the seam so sections never end on a hard edge. */
.has-wash > .wash::after {
  content: ''; position: absolute; inset-inline: 0; bottom: 0; height: 200px;
  background: linear-gradient(to top, var(--paper), rgba(255, 255, 255, 0));
}

/* ————————————————————————————————————————————————
   Pills. Filled and ghost share geometry so they sit as a matched pair.
   ———————————————————————————————————————————————— */
.pill, .ghost {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  height: 48px; padding-inline: 26px; border-radius: var(--r-pill);
  font-size: 16px; font-weight: 450; letter-spacing: -0.005em;
  white-space: nowrap; cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.pill { background: var(--ink); color: var(--paper); border: 1px solid var(--ink); }
.pill:hover { background: #2b2e33; border-color: #2b2e33; }
.ghost { background: transparent; color: var(--ink); border: 1px solid var(--ink); }
.ghost:hover { background: var(--ink); color: var(--paper); }
.pill:active, .ghost:active { transform: translateY(1px); }
.pill--sm, .ghost--sm { height: 42px; padding-inline: 20px; font-size: 15px; }

/* Lowest-emphasis link: the arrow carries the affordance, not an underline. */
.alink {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 16px; font-weight: 450; color: var(--ink);
}
.alink svg { transition: transform var(--dur) var(--ease-out); }
.alink:hover svg { transform: translateX(4px); }
.alink:hover span { text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }

/* ————————————————————————————————————————————————
   Surfaces
   ———————————————————————————————————————————————— */
/*
 * ONE CARD, TWO VARIANTS — and nothing else. The card is the most repeated
 * object on the site, and it had drifted into six grounds doing the same job:
 * mist, white-with-a-hairline, transparent-with-a-hairline, white-no-border,
 * white-with-a-top-only-radius and white-on-a-heavier-hairline. Two of them
 * shipped on ONE page, three sections apart, on the same six-card isometric
 * grid. Sections must not restate background, border, radius or padding here.
 *
 *   .card         mist, no border — the default, and what a grid of drawings
 *                 sits on, on paper and on fog alike.
 *   .card--paper  white and a hairline — reserved for the one case mist cannot
 *                 do: a card that has to lift off a filled ground. It is down
 *                 to a single user (customers-more); nothing else may take it,
 *                 and no section may invent a third.
 */
/* --illo-ground is the surface a drawing's occlusion silhouette is painted in.
   It is declared beside the background it has to match, so the two can never
   drift apart; see the illustration block below. */
.card { background: var(--mist); --illo-ground: var(--mist); border-radius: var(--r-card); padding: var(--s32); }
.card--paper { background: var(--raised); --illo-ground: var(--raised); border: 1px solid var(--hair); }
.card__title { font-size: var(--t-body-lg); font-weight: 500; letter-spacing: -0.01em; margin-bottom: 10px; }
/*
 * The ONE permitted louder card title, for the lead of a grid. Same family and
 * same weight as its siblings — a lead that changes typeface puts two families
 * at one rank inside a single grid, which is what three sections were doing.
 */
.card__title--lead { font-size: var(--t-h-sm); letter-spacing: var(--tr-h-sm); line-height: 1.22; }
.card__body { font-size: 16px; line-height: 1.55; color: var(--slate); }

/* Rationed to one per page. Warm ink on warm paper — never on a tinted section. */
.card--peach { background: var(--peach); color: var(--sienna); --illo-ground: var(--peach); }
.card--peach .card__title, .card--peach .card__body, .card--peach p { color: var(--sienna); }

/* The only elements with visible elevation. */
.artifact {
  background: var(--raised); border-radius: var(--r-elevated); box-shadow: var(--sh-artifact);
}

.rule { height: 1px; background: var(--hair); border: 0; margin: 0; }

/* ————————————————————————————————————————————————
   Nav
   ———————————————————————————————————————————————— */
.nav {
  position: sticky; top: 0; z-index: 100; height: 76px; display: flex; align-items: center;
  transition: background var(--dur) var(--ease-out), border-color var(--dur) var(--ease-out),
              backdrop-filter var(--dur) var(--ease-out);
  border-bottom: 1px solid transparent;
}
.nav.is-stuck {
  background: rgba(255, 255, 255, 0.82);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  border-bottom-color: var(--hair);
}
/*
 * NO `width: 100%` HERE. .nav__inner also carries .wrap, and an equal-specificity
 * `width` declared later overrides the 1200px column .wrap exists to set — which
 * pushed the logo and the CTA hard against the viewport edges and left the nav
 * links misaligned against every other element on the page. Most visible on wide
 * displays, but it was wrong at every width.
 */
.nav__inner { display: flex; align-items: center; justify-content: space-between; gap: 24px; }
.nav__logo { display: inline-flex; align-items: center; gap: 9px; flex: 0 0 auto; white-space: nowrap;
             font-family: var(--serif); font-size: 21px; letter-spacing: -0.015em; }
.nav__logo em { font-style: italic; }
.nav__links { display: flex; align-items: center; gap: 34px; }
.nav__links a { font-size: 16px; font-weight: 400; color: var(--ink); position: relative; padding-block: 4px; }
.nav__links a::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: var(--ink); transform: scaleX(0); transform-origin: left;
  transition: transform var(--dur) var(--ease-out);
}
.nav__links a:hover::after, .nav__links a.is-active::after { transform: scaleX(1); }
.nav__cta { display: flex; align-items: center; gap: 22px; }
/* A text link in the nav is still a tap target: it was 21px tall, under the 24px
   floor and well under the 44px comfortable size on touch. */
.nav__cta .alink { min-height: 44px; }
.nav__burger { display: none; width: 44px; height: 44px; align-items: center; justify-content: center;
               background: none; border: 0; cursor: pointer; margin-right: -10px; }

@media (max-width: 940px) {
  .nav__links, .nav__cta .alink, .nav__cta .ghost { display: none; }
  .nav__burger { display: inline-flex; }
}
@media (max-width: 360px) {
  .nav__cta > .pill { display: none; }
}

.navdrawer {
  position: fixed; inset: 0; z-index: 200; background: var(--paper);
  display: flex; flex-direction: column; padding: 20px var(--gutter) 40px;
  opacity: 0; visibility: hidden; transform: translateY(-8px);
  transition: opacity var(--dur) var(--ease-out), transform var(--dur) var(--ease-out), visibility var(--dur);
}
.navdrawer.is-open { opacity: 1; visibility: visible; transform: none; }
.navdrawer__top { display: flex; align-items: center; justify-content: space-between; height: 56px; }
.navdrawer__links { display: flex; flex-direction: column; gap: 4px; margin-top: 32px; }
.navdrawer__links a { font-family: var(--serif); font-size: 34px; line-height: 1.5; letter-spacing: -0.015em; }
.navdrawer__foot { margin-top: auto; display: flex; flex-direction: column; gap: 12px; }
.navdrawer__foot .pill, .navdrawer__foot .ghost { width: 100%; }

/* ————————————————————————————————————————————————
   Footer
   ———————————————————————————————————————————————— */
.footer { border-top: 1px solid var(--hair); padding-block: 72px 56px; background: var(--paper); }
.footer__grid { display: grid; grid-template-columns: 1.6fr 1fr 1fr 1fr; gap: 48px; }
.footer__brand p { margin-top: 14px; max-width: 260px; font-size: 15px; line-height: 1.55; color: var(--slate); }
.footer__col .footer__coltitle { font-size: 14px; font-weight: 450; color: var(--ash); margin-bottom: 18px; }
.footer__col a { display: block; font-size: 15px; line-height: 1; padding-block: 9px; color: var(--ink); }
.footer__col a:hover { color: var(--slate); }
.footer__base { display: flex; justify-content: space-between; gap: 20px; flex-wrap: wrap;
                margin-top: 64px; padding-top: 28px; border-top: 1px solid var(--hair);
                font-size: 14px; color: var(--ash); }
@media (max-width: 860px) {
  .footer__grid { grid-template-columns: 1fr 1fr; gap: 40px 24px; }
  .footer__brand { grid-column: 1 / -1; }
}

/* ————————————————————————————————————————————————
   THE ILLUSTRATION SYSTEM
   Every drawing on the site must obey these, or the page reads as clip-art
   assembled by six different people — which is exactly what reviewers found:
   "five different conventions" across six cards in one section.
   ———————————————————————————————————————————————— */
.illo {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
  color: var(--ink);
  /* Strokes must survive the warm ground. A floor plan drawn at low contrast was
     measured as "effectively does not exist" — opacity belongs on the wrapper,
     never on individual strokes. */
  opacity: 0.78;
}
.illo--quiet { opacity: 0.52; }
.card--peach .illo,
.illo--sienna { color: var(--sienna); }
.illo--invert { color: var(--raised); }

/* One stroke weight for the whole site. Anything thinner disappears on paper;
   anything heavier reads as an icon rather than a drawing. */
.illo [stroke],
.illo path, .illo line, .illo circle, .illo rect, .illo ellipse, .illo polyline, .illo polygon {
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
  vector-effect: non-scaling-stroke;   /* weight stays 1.4 at any scale */
}
/* The single permitted fill: a solid accent mark, used sparingly for emphasis. */
.illo .illo-fill { fill: currentColor; stroke: none; }
/* Construction lines — hidden faces, guides — sit back without changing weight. */
.illo .illo-back { opacity: 0.34; }

/* ————————————————————————————————————————————————
   HIDDEN-LINE REMOVAL — and it lives HERE, not in a section file.

   ILLUSTRATION.md item 5 makes occlusion mandatory: where one object sits in
   front of another, the object behind must BREAK, or the drawing is an x-ray.
   The tool that does it is an opaque silhouette painted in whatever surface the
   drawing stands on, laid down immediately before the object it belongs to.

   It used to be declared locally, in two section files out of thirteen, and
   that is most of why eleven sections carried no occlusion at all: an author
   who copied class="illo-mask" anywhere else got `fill: none` from the generic
   rule above and the drawing silently rendered as an x-ray. No error, no
   missing style — just a worse drawing. Declared globally, the class works
   wherever it is written.

   The surface comes from --illo-ground, set once per card beside its own
   background (.card / .card--paper / .card--peach above). A drawing standing on
   bare paper needs to set nothing; the fallback is the page.
   ———————————————————————————————————————————————— */
.illo .illo-mask { fill: var(--illo-ground, var(--paper)); stroke: none; }

/* ————————————————————————————————————————————————
   TWO SIZES, AND NO THIRD.

   Measured across the site, .illo boxes ran from 45px to 1102px — a 24x range
   under one class, which makes the class mean nothing, and inside a grid of
   peers makes size read as importance (ILLUSTRATION.md items 3 and 7).
   A drawing is now either a card mark or a section's feature. Both are
   CEILINGS paired with width:100%, so a narrow column shrinks its drawing
   instead of earning a size of its own.
   ———————————————————————————————————————————————— */
.illo--card { width: 100%; max-width: var(--illo-card); }
.illo--feature { width: 100%; max-width: var(--illo-feature); }

/* ————————————————————————————————————————————————
   BLEED STAGE — for absolutely-positioned decoration around a content column.
   `position:absolute; inset:0` on a full-bleed section anchors children to the
   VIEWPORT, so on a wide display the floating cards fly out to the edges and
   lose all relationship to the headline they are meant to frame. This bounds the
   layer to the content column plus a fixed bleed, centred, so the composition
   holds at any width and only ever crops by --bleed.
   ———————————————————————————————————————————————— */
.stage {
  position: absolute;
  top: 0; bottom: 0; left: 0; right: 0;
  width: min(100%, calc(var(--page) + var(--bleed, 240px) * 2));
  margin-inline: auto;
  pointer-events: none;
}

/* ===== 30-motion.css ===== */
/*
 * Motion primitives. Everything is opt-in via a data attribute so a section
 * author never writes keyframes — and everything collapses to "already visible"
 * under prefers-reduced-motion, which is the accessible default, not an afterthought.
 */
[data-reveal] {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
}
[data-reveal].is-in { opacity: 1; transform: none; }

/* Headline reveal: lines rise from a clipped baseline, staggered. */
[data-reveal-lines] .line-mask { display: block; overflow: hidden; }
[data-reveal-lines] .line-mask > span {
  display: block;
  transform: translateY(105%);
  transition: transform 900ms var(--ease-out);
  transition-delay: var(--line-delay, 0ms);
}
[data-reveal-lines].is-in .line-mask > span { transform: none; }

/* Floating artifacts drift in from their resting offset. */
[data-float] {
  opacity: 0;
  transform: translate3d(var(--float-x, 0), var(--float-y, 26px), 0) scale(0.985);
  transition: opacity 1000ms var(--ease-out), transform 1000ms var(--ease-out);
  transition-delay: var(--float-delay, 0ms);
}
[data-float].is-in { opacity: 1; transform: translate3d(0, 0, 0) scale(1); }

/* Idle bob for hero artifacts, once they have landed. */
@keyframes lp-bob {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50%      { transform: translate3d(0, -7px, 0); }
}
[data-float].is-in[data-bob] {
  animation: lp-bob var(--bob-dur, 7s) var(--ease-in-out) infinite;
  animation-delay: var(--bob-delay, 0s);
}

/* Counters and bars */
[data-count] { font-variant-numeric: tabular-nums; }
[data-bar] { transform: scaleX(0); transform-origin: left; transition: transform 1100ms var(--ease-out); }
[data-bar].is-in { transform: scaleX(1); }

/* Drawn SVG strokes */
[data-draw] {
  stroke-dasharray: var(--len, 1000);
  stroke-dashoffset: var(--len, 1000);
  transition: stroke-dashoffset 1600ms var(--ease-out);
}
[data-draw].is-in { stroke-dashoffset: 0; }

@media (prefers-reduced-motion: reduce) {
  [data-reveal], [data-float], [data-bar], [data-draw],
  [data-reveal-lines] .line-mask > span {
    opacity: 1 !important;
    transform: none !important;
    stroke-dashoffset: 0 !important;
    transition: none !important;
    animation: none !important;
  }
}

/* ===== section:hardware-hero ===== */
/* ————————————————————————————————————————————————
   hardware-hero — the Hardware page hero.

   Two elements of type on the wash and one relationship between them, and the
   relationship is the section: the sans block is set INTO the notch the
   headline's rag cuts, sharing the italic's last baseline.

   ROUND 2 — the device was described here and did not render. Three named
   defects, all mechanical, all fixed below and nowhere else:

     1. THE NUDGE POINTED THE WRONG WAY. `align-items: end` aligns BOXES; a
        negative bottom margin on a bottom-aligned item drives it DOWN. The
        `-8px` written to close a 5px baseline error opened it to 13px instead.
        It is now a POSITIVE margin, and it is no longer a guess — it is the
        arithmetic difference between the two boxes' space-below-last-baseline,
        so it re-derives itself at every type step (see .hh-sub).
     2. THE INTERLOCK NEVER EXISTED. Column 1 was `auto`, so it sized to the
        LONGEST line and the gutter was measured off line 1. The sub therefore
        started 95px clear of the longest line and 405px clear of the italic —
        beside the rag, never in it. Column 1 is now sized off LINE 2 and line 1
        is allowed to overhang it, so the sub's block overlaps the headline's by
        ~76px in x. Overlap in x is what makes it an interlock.
     3. NOTHING CROSSED ANYTHING. Every mark sat politely inside the measure.
        The headline overhangs its own track into the sub's column, which is the
        crossing this section is composed around. (It used also to hang the
        whole display block 64px LEFT of the page rail above 1400 — a private
        break of the one rail the rest of the site obeys, now deleted.)

   And one hairline is added — the only non-type mark in the section. It runs
   from the italic's terminal to the sub's left edge ON the shared baseline, so
   the alignment the section stakes itself on is drawn rather than asserted, and
   the quietest mark in the frame finally has something to be quiet against.
   ———————————————————————————————————————————————— */

/*
 * THE HEIGHT IS THE SHARED HERO STAGE, NOT A LOCAL ONE.
 *
 * This section used to raise --section-y to 168 so the wash-under-nav rule in
 * 10-base.css computed a 244px head, and then set a 236px foot — the tallest
 * opening and the deepest close on the site, on the same 76px nav every other
 * page carries. Both now come from that shared rule: --hero-y above, the loose
 * section rhythm below.
 *
 * The wash's blooms are sized as PERCENTAGES of the section box, so a shorter
 * section gets the same gradients in a shallower band. The bloom pair below is
 * already re-composed for this section (two fields, not the default four), and
 * the 200px fade at the foot still dissolves it into paper, so the band closes
 * on paper and never on a visible edge.
 *
 * THE TYPE IS TOP-WEIGHTED, AND THAT IS THE CHOICE — not a centring that missed.
 * The block is hung from the top of the field like a title page, with the
 * longer fall underneath.
 */
.sec-hardware-hero {

  /* CLIP THE X AXIS ONLY, as every hero on this site does. `.has-wash > .wash`
     is inset -5% on both flanks and that overhang counts toward document
     scrollWidth even under the root's overflow-x: clip. Y stays visible so the
     wash's bottom fade is never guillotined into a grey band at the seam. */
  overflow-x: clip;
  overflow-y: visible;
}

/* — THE GROUND —————————————————————————————————————
 *
 * The shared four-bloom wash is a page-level texture and knows nothing about
 * where this section puts its ink. Left as the default it put a purple core in
 * the top-left behind the eyebrow and a cool one at 90%/74% directly under the
 * sub — the two loudest patches in the frame sitting on the two quietest pieces
 * of type in it, with the payoff on flat ground. So it is re-registered here,
 * scoped to this section and nowhere else, and cut from four blooms to two.
 *
 * Measured at 1440: the section box is 1440 x 748 with its top at -76, and the
 * wash is inset -10% top / -5% flanks, so it measures 1584 x 823 with its
 * origin at (-72, -151). Every percentage below is arithmetic against that box.
 *
 *   1. THE WARM CORE SITS IN THE NOTCH, not on the italic. Centring it on the
 *      italic's own box (17% / 68%) was tried and rejected from the raster: at
 *      any radius wide enough to ground the headline column it ran off the left
 *      edge of the frame, and a bloom cut by the page edge reads as a stain
 *      rather than as light. `33% 62%` puts the core at (450, 361) — the open
 *      triangle the headline's rag cuts, right of "buy it from us" and above
 *      "at cost." The negative space the composition makes is the brightest
 *      ground in the section, and the italic sits at t=0.34 of the ramp, warm
 *      but not spotlit. It is also, now, the ground the hairline crosses.
 *
 *      IT IS BIG ON PURPOSE. A first pass used a compact 36% x 32% ellipse held
 *      at full --wash-1 for its first 6%, and it rendered as an OBJECT: a
 *      readable orange oval with a findable edge, sitting on the page like a
 *      sticker. A wash has to read as light, which means low peak against a
 *      long ramp. The plateau is gone and the radius is half the wash box, so
 *      the same tint spreads across 570px of falloff instead of 365px and never
 *      draws its own outline.
 *
 *   2. THE COUNTERWEIGHT, high and right, at --wash-2 — the palest of the four
 *      tints. It gives the empty top-right quadrant a ground without ever
 *      threatening to be the brightest thing in the frame. Its diagonal runs
 *      lower-left to upper-right, which is the OPPOSITE of the type's
 *      top-left-to-bottom-right read: the two cross, and both ends of the
 *      reading diagonal stay on quiet paper.
 *
 * The two cool tints are dropped. On a section made of three typographic marks
 * they are two more candidates for a focal point that is already spoken for.
 * Zero-alpha stops are spelled as rgba() of the same tint, never `transparent`
 * — that is rgba(0,0,0,0), and interpolating to it drags a warm ramp through
 * grey and leaves a dirty edge.
 */
.sec-hardware-hero > .wash {
  background:
    radial-gradient(50% 44% at 33% 62%,
      var(--wash-1) 0%, rgba(255, 217, 194, 0) 72%),
    radial-gradient(52% 44% at 74% 22%,
      var(--wash-2) 0%, rgba(255, 233, 214, 0) 76%);
}

/*
 * ABOVE THE MEASURE CAP, THE WASH IS PINNED TO THE TYPE, NOT TO THE VIEWPORT.
 *
 * .wrap stops growing at 1200 but the wash box does not, so a percentage that
 * lands in the notch at 1440 lands somewhere else at 1728: measured there, the
 * core had slid to x=532 against a headline starting at 264 — behind the type
 * rather than beside it, and warm enough at the left edge (t=0.22) to show a
 * blush in the margin. Both centres are re-expressed as an offset from the
 * frame's midline, which is also the measure's midline, so they hold their
 * position against the ink at any width above the cap: -270px is the notch,
 * +380px is the top-right quadrant. At 1440 the calc and the percentage give
 * the same pixel, and at 1264 — where the cap starts binding — they are 34px
 * apart on a 950px ellipse, which is nothing. Below the cap the percentages
 * are already correct, because there the measure tracks the viewport.
 */
@media (min-width: 1264px) {
  .sec-hardware-hero > .wash {
    background:
      radial-gradient(50% 44% at calc(50% - 270px) 62%,
        var(--wash-1) 0%, rgba(255, 217, 194, 0) 72%),
      radial-gradient(52% 44% at calc(50% + 380px) 22%,
        var(--wash-2) 0%, rgba(255, 233, 214, 0) 76%);
  }
}

/* — THE INTERLOCK ——————————————————————————————————
 *
 * Three authored headline lines, measured at the 64px ceiling with their true
 * sidebearings: 470.4 / 350.5 / 164.5, which in ems of the headline size are
 * 7.35 / 5.48 / 2.57. That rag tapers hard and cuts a triangular void down the
 * right of the block, and the whole job of this grid is to put the sans INTO
 * that void.
 *
 * The previous build used `grid-template-columns: auto`, which sizes track 1 to
 * the widest line — so the gutter was measured off LINE 1 and the taper was
 * never engaged at all. Track 1 is now sized off LINE 2 (--hh-l2), and the
 * headline is given `width: max-content` so line 1 simply OVERHANGS the track
 * by 122px and crosses into the sub's column. Nothing is clipped: `.line-mask`
 * hides overflow but the mask is as wide as the h1's max-content box, so the
 * lines never meet an edge.
 *
 * The numbers that follow from it, at 1440 with no hang:
 *   line 1 ends    588.8      sub starts     512.8   → 76px of x-overlap
 *   line 2 ends    468.8      gutter          44px
 *   line 3 ends    282.8      hairline    244 → 423
 * The sub is 230px of pure gutter clear of the italic, and that gap is the
 * composition's one piece of held air; the hairline crosses it.
 */
.sec-hardware-hero .hh-row {
  /* Line widths as multiples of the headline size, so every derived offset
     re-computes itself when --t-h-lg steps down a breakpoint instead of being
     re-tuned by hand. */
  --hh-l2: calc(var(--t-h-lg) * 5.45);   /* "buy it from us" */
  --hh-l3: calc(var(--t-h-lg) * 2.54);   /* "at cost."       */
  --hh-gap: 44px;

  display: grid;
  grid-template-columns: var(--hh-l2) minmax(0, 1fr);
  column-gap: var(--hh-gap);
  align-items: end;
}

/*
 * THE MEASURE HANG IS GONE.
 *
 * Above 1400 this block used to step 64px LEFT of the page rail while its own
 * eyebrow held it, putting the title at x=54 and the label at x=120 on the same
 * section — a private convention no other page shared and the largest single
 * break of the rail on the site. What made it read as a deliberate indent was
 * never the hang; it was the sidebearing correction bundled into the same
 * value. That half is right, so it has been promoted to the shared serif
 * headline rule in 10-base.css and now runs on every page. This block keeps the
 * OVERHANG of line one past its own track, which is the section's real move.
 */
.sec-hardware-hero .hh-title {
  grid-column: 1;
  /* Wider than its track, deliberately — this is the overhang. */
  width: max-content;
  max-width: none;
}

.sec-hardware-hero .hh-sub {
  grid-column: 2;
  position: relative;
  /*
   * 17.5em of the sub's own size — a COLUMN, not a banner, and the measure is
   * chosen off the raster rather than picked. At 640 the sentence broke 638/251,
   * a rag so lopsided it read as an accident, and two lines of 29px stood only
   * 58px tall — shorter than one headline line, so bottom-aligned the whole
   * block sat beside "at cost." and never reached the taper at all. Swept from
   * 340 to 640: everything in [333, ~370] breaks 333/299/251, which is three
   * lines DESCENDING — the same direction as the serif rag above it — and 87px
   * tall, so the block now spans the headline's last two lines and its top line
   * sits in the 44px gutter beside "buy it from us". 350px is the middle of
   * that window, with ~17px of slack either side for font-rendering drift.
   *
   * Stated in the sub's OWN em, so it survives every breakpoint untouched: at
   * 20px it is 350 against a 353px longest headline line, at 16px it is 280
   * against 309, and it breaks 272/244/205 on the phone. One measure, and the
   * sans column is always about as wide as the widest line of serif above it.
   */
  max-width: 17.5em;
  /*
   * THE ONE ALIGNMENT THAT HAS TO BE RIGHT, and it is now derived, not guessed.
   *
   * `align-items: end` aligns the two BOXES. What has to align is the last
   * BASELINES, and each box carries a different amount of space below its own:
   * measured, the headline carries 13.43px below its last baseline (0.2099 of
   * 64px) and the 20px sub carries 8.04px (0.402 of 20px). The difference is
   * how far the sub has to rise, and a POSITIVE bottom margin is what raises a
   * bottom-aligned grid item — the previous build wrote -8px here, which drove
   * it down and turned a 5px error into 13px.
   *
   * Written as the difference of the two coefficients so it re-derives itself:
   * 5.4px at 64/20, 2.0px at 48/20, and it is dropped entirely once the layout
   * stacks and the alignment it serves no longer exists.
   */
  margin-bottom: calc(var(--t-h-lg) * 0.2099 - var(--t-body-lg) * 0.402);
}

/*
 * THE HAIRLINE. The section's only mark that is not a letter.
 *
 * It sits ON the shared baseline — its own bottom edge is the baseline, found
 * by the same 0.402 coefficient the margin above uses — and runs from 26px past
 * the italic's terminal to 26px short of the sub's left edge. Two jobs:
 *
 *   · it DRAWS the alignment the section is built on, instead of asking the eye
 *     to trust a claim about it across 230px of empty ground;
 *   · it gives the italic a foil. "at cost." is deliberately the smallest and
 *     quietest mark in the frame, and understatement needs something to be
 *     quiet against; a 1px rule leaving its terminal is the least that can be.
 *
 * Its width is stated as gutter arithmetic (l2 + gutter - l3 - the two 26px
 * standoffs), so it stays correct at every headline size and is unaffected by
 * the hang, which moves the italic and the sub by the same amount.
 */
.sec-hardware-hero .hh-sub::before {
  content: '';
  position: absolute;
  right: 100%;
  bottom: calc(var(--t-body-lg) * 0.402);
  margin-right: 26px;
  width: calc(var(--hh-l2) + var(--hh-gap) - var(--hh-l3) - 52px);
  height: 1px;
  background: var(--hair-strong);
}

/* — Narrow ———————————————————————————————————————— */

/* The tablet band. The headline drops to 48px with the token and every derived
   offset follows it, so the interlock survives intact; only the section's air
   and the gutter come in. */
@media (max-width: 1080px) {
  .sec-hardware-hero .hh-row { --hh-gap: 36px; }
}

/*
 * THE INTERLOCK IS RETIRED EARLY, ON PURPOSE. Two columns still fit below this,
 * but not with any margin, and the failure mode is not a wrap — it is the sub's
 * measure collapsing under 45 characters while the headline still overhangs it.
 * Stacking at 1000, while track 1 is still a clear 130px from trouble, is the
 * only version of this that cannot fail quietly. Stacked, the sub goes back on
 * the rail under the headline, and BOTH the baseline nudge and the hairline are
 * removed with the alignment they served — a rule pointing at nothing is worse
 * than no rule.
 */
@media (max-width: 1000px) {
  .sec-hardware-hero .hh-row {
    grid-template-columns: minmax(0, 1fr);
    row-gap: 36px;
  }
  .sec-hardware-hero .hh-title,
  .sec-hardware-hero .hh-sub { grid-column: 1; }
  .sec-hardware-hero .hh-title { max-width: 100%; }
  .sec-hardware-hero .hh-sub { margin-bottom: 0; }
  .sec-hardware-hero .hh-sub::before { display: none; }
}

@media (max-width: 720px) {
  .sec-hardware-hero .hh-row { row-gap: 26px; }

  /*
   * THE HEADLINE TAKES --t-h-lg (34px here) LIKE EVERY OTHER PAGE TITLE. It
   * used to be sized to the measure instead — width / 7.75, i.e. 42px at 390 —
   * which is a per-section divisor, and six of those across six inner heroes
   * put a 1.8x spread on one rank. The authored three-line rag holds at 34px
   * with room to spare; the sub drops to 16px so the jump still reads.
   */
  .sec-hardware-hero .hh-sub { font-size: 16px; }

  /*
   * The wash is re-derived here, not inherited, because BOTH of its terms
   * change at 390. The box is 429 x 630 rather than 1584 x 823, so the desktop
   * `50% 44%` ellipse is 215 x 277 — taller than it is wide against a frame
   * that is the other way round, which fills the phone edge to edge and stops
   * being a bloom at all. And the layout has stacked, so the ink it was aimed
   * at has moved: the notch it sat in does not exist once the sub is under the
   * headline rather than beside it.
   *
   * So: one wide, shallow warm field lying across the whole headline block,
   * and one pale haze in the top-right corner. Flattened to 26% of the height,
   * they read as light falling across a narrow column instead of as two discs.
   */
  .sec-hardware-hero > .wash {
    background:
      radial-gradient(84% 26% at 40% 56%,
        var(--wash-1) 0%, rgba(255, 217, 194, 0) 76%),
      radial-gradient(70% 22% at 82% 18%,
        var(--wash-2) 0%, rgba(255, 233, 214, 0) 78%);
  }
}

/* ===== section:hardware-pieces ===== */
/* ————————————————————————————————————————————————————————————————
   hardware-pieces — four counter pieces on one counter.

   THE DECISION IS COLOUR AND RANK, NOT SIZE. An earlier cut made plate 01 a
   third wider and 64px taller and put the same drawing, the same title and the
   same two lines inside it: the extra height bought void, not emphasis, and the
   row read as four equal catalogue thumbnails. So the hierarchy lives in FOUR
   moves that all land on one plate — warm surface, warm ink, a name one size up,
   and the only sentence in the row — and the other three keep a name and nothing
   else. That last move is the one this file kept asserting and never made: every
   plate shipped a two-line sentence, so the lead was a recoloured sibling rather
   than a different rank, and the peach was carrying the hierarchy alone.

   The page's peach ration is spent here. hardware-built.css declares it
   "unspent and stays unspent" and hardware-hero uses a wash, not the surface,
   so hardware.html has exactly one peach surface and this is it.

   Three edges do the rest of the composition:
     - ONE left edge, and it is the site's. Header and plate row both sit on
       the 1200 page rail. An earlier cut ran the sentence on 1200 and the
       plates on 1400, an 89px mismatch on both sides — too big to read as
       optical correction, too small to read as an indent, so nothing in the
       section shared an edge; the cut after it put BOTH on 1400, which fixed
       the section and broke the page, leaving this headline 88px left of its
       neighbours'.
     - ONE bottom edge, and it is not a card edge: a 100vw hairline the plates
       stand on and occlude, surviving only in the gutters and off both screens.
     - Tops that STEP DOWN to the right by exactly one 22px step per plate, so
       the row rakes the same way the header does. That step is DECLARED as a
       margin on each plate, not inherited from how its sentence wrapped.
   And one thing crosses a boundary: plate 01's terminal is drawn out of the top
   of its own plate and stands on the fog — and the plate's top edge is TANGENT
   to the monitor head's lower-left corner, not a horizontal cut through its
   face. See --hp-cross below for why that is a fraction and not a pixel count.
   ———————————————————————————————————————————————————————————————— */

/* A strip, not a stage: the four pieces are a rack under one line of type, so
   the section takes the TIGHT rhythm at every width. It used to run the normal
   96 on desktop and a private 40 on the phone. */
.sec-hardware-pieces { padding-block: var(--section-y-tight); }

/* ── Header ──────────────────────────────────────────────────────────
   Not a stack. The headline holds the left seven twelfths; the eyebrow is
   parked at the far right ON THE LAST BASELINE of the headline, so the eye
   travels the diagonal from label to accent rather than straight down. */
.sec-hardware-pieces .hp-head {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 4fr);
  column-gap: var(--s48);
  margin-bottom: calc(var(--s96) + var(--s16));
}
.sec-hardware-pieces .hp-title {
  grid-column: 1;
  grid-row: 1;
  max-width: 34ch;
}
.sec-hardware-pieces .hp-eyebrow {
  grid-column: 2;
  grid-row: 1;
  justify-self: end;
  align-self: end;
  /* Bottom-aligned to the headline's last baseline, so the component's gap
     underneath would push it off that baseline. The grid owns the spacing. */
  margin-bottom: 0;
  /* Sits the label's baseline on the headline's last baseline rather than on
     its box, which is 8px lower and reads as a near-miss. */
  padding-bottom: 0.42em;
  text-align: right;
}

/* ── The counter ─────────────────────────────────────────────────────
   One hairline at the foot of the plate row, full viewport width, painted
   BEFORE the plates so they occlude it. The plates rest on it; it does not
   frame them. The rail never clips: plate 01's drawing leaves it at the top. */
.sec-hardware-pieces .hp-rail { position: relative; }
.sec-hardware-pieces .hp-shelf {
  position: absolute;
  left: 50%;
  bottom: 0;
  width: 100vw;
  height: 1px;
  transform: translateX(-50%);
  background: var(--hair-strong);
}

.sec-hardware-pieces .hp-row {
  position: relative;
  display: grid;
  /* How wide a drawing is allowed to be: the inner width of the NARROWEST
     plate in the row, less 10px for the vertical scrollbar 100vw does not know
     about. Every drawing is sized off this ONE number, because the global
     `svg { max-width: 100% }` otherwise clamps only the narrow plates and the
     terminal quietly grows 20% larger than the printer at the widths nobody
     screenshots — measured, and the reason the four read as one family.
     The cap itself is --illo-card (00-tokens.css), the site's one card-drawing
     size, so these four are in scale with every other card mark on the site
     and not merely with each other. */
  --plate-fit: calc((min(var(--page), 100vw - var(--gutter) * 2) - var(--s16) * 3) / 4.34
                    - var(--s20) * 2 - 10px);
  --draw-w: min(var(--illo-card), var(--plate-fit));
  /* The lead plate is wider, but width is the least of what marks it. */
  grid-template-columns: 1.34fr 1fr 1fr 1fr;
  gap: var(--s16);
  /* ONE bottom edge, and every top DECLARED — see --hp-rake below. The plates
     stretch to the row, so each one's top is its own margin and its bottom is
     the counter. This used to be `align-items: end`, which made every top a
     by-product of how that plate's sentence happened to wrap. */
}

/* ── Plates ──────────────────────────────────────────────────────────
   Ground, radius and padding are the .card component's, and this file no longer
   restates any of them. The plates used to be white with a TOP-ONLY radius on
   three different top paddings — a fifth card ground and a fourth card padding,
   for the same object the rest of the site draws once.

   THE RAKE IS NOW DECLARED, NOT EMERGENT, and that is the one structural change
   this file needed. It used to be interior air above each drawing on a row that
   bottom-aligned, which meant a plate's top edge was (its content) tall — and
   its content was however many lines its sentence happened to take. Measured
   across the desktop range the four sentences ran 3/2/2/2 lines at 1440, 5/2/2/3
   at 1120 and 5/2/3/3 at 1000; a wrapped line is 25px and the rake step is 22px,
   so reflow outvoted the design. The tops came out 308 / 278 / 300 / 322 at
   1440 — plate 02 ABOVE the lead — and at 1120 plate 04 sat above plate 03,
   which is the rake running backwards.

   So: the plates STRETCH to the row and each declares its own top as a margin.
   Top = the rake, bottom = the counter, at every width, whatever the text does.
   And because a grid row is sized by the tallest item PLUS its margin, a plate
   can never overflow the box it was stretched into. */
.sec-hardware-pieces .hp-card {
  position: relative;
  display: flex;
  flex-direction: column;
  margin-top: var(--hp-rake, 0px);
}
.sec-hardware-pieces .hp-card--b { --hp-rake: 22px; }
.sec-hardware-pieces .hp-card--c { --hp-rake: 44px; }
.sec-hardware-pieces .hp-card--d { --hp-rake: 66px; }
/* The other half of the same idea: the drawing hangs from its plate's top edge
   and the words sit on the counter. All four last lines therefore share ONE
   baseline off the bottom — measured, 650px on all four at 1440 — and the slack
   a stretched plate is given opens between the object and its name, where it
   reads as air, instead of dropping the name away from the edge everything else
   is measured from. */
.sec-hardware-pieces .hp-text { margin-top: auto; }

.sec-hardware-pieces .hp-n {
  position: absolute;
  top: var(--s16);
  right: var(--s20);
  font-size: 13px;
  font-style: normal;
  font-weight: 450;
  letter-spacing: 0.07em;
  color: var(--ash);
}
.sec-hardware-pieces .hp-card--lead .hp-n {
  top: var(--s24);
  right: var(--s32);
  color: var(--sienna);
  opacity: 0.5;
}

/* ── The drawings ────────────────────────────────────────────────────
   One width for all four. max-width is switched off so nothing can clamp one
   plate and not another behind our back. */
.sec-hardware-pieces .hp-mark {
  display: flex;
  justify-content: center;
  margin-bottom: var(--s16);
}
.sec-hardware-pieces .hp-mark .illo {
  width: var(--draw-w);
  height: auto;
  max-width: none;
}
/* THE ONE THING THAT CROSSES A BOUNDARY — and it now crosses at a JOINT.

   --hp-cross is 61/146: the viewBox line the plate's top edge is asked to land
   on, and 61 is the monitor head's lower-left corner. The whole screen aperture,
   its four lines and the drawing's one solid accent sit above that line on the
   fog; the rest of the head, the stand and the plinth sit below it on the peach.

   It used to be 72 FIXED PIXELS, and two things followed. The cut landed
   wherever 72px happened to fall — mid-face, through the middle of the screen,
   which reads as a card drawn too short rather than as a card the object stands
   out of. And it drifted: --draw-w is 215px at 1440 and 201px at 1200, so the
   same 72px was a different line in the drawing at every width, and a tangency
   cannot be authored against a moving target. Expressed as a fraction of the
   drawing it is the same joint everywhere.

   The second half of the same fix is that the overhang now carries INK. Measured
   on the old cut, the escaping half's darkest pixel was 111 on the fog while the
   contained half's was 87 on the peach — thin outline against outline plus a
   solid — so the boldest gesture in the section was also its faintest mark. The
   solid accent is drawn above the joint now, and the contained half keeps its
   own on the guest panel, so neither half of the crossing is the pale one. */
.sec-hardware-pieces .hp-card--lead .hp-mark {
  --hp-cross: 0.4178;
  margin-top: calc(var(--s32) * -1 - var(--draw-w) * var(--hp-cross));
  margin-bottom: var(--s40);
  /* And it is optically LEFT-set, not centred. The terminal's ink starts
     15.60% into its own viewBox (x=22.8 of 146), so the svg is pulled that far
     back out of the padding and the drawing's left edge lands exactly on the
     sentence's — one column, not a centred picture parked above left-set text.
     Centred, the drawing missed the text by 45px, which is the same near-miss
     the section's two measures used to make. */
  justify-content: flex-start;
  margin-left: calc(var(--draw-w) * -0.156);
}
/* The lead plate's air is spent BELOW the drawing, never above it: the crossing
   is measured off the plate's top edge, so anything added above the
   object would eat the crossing instead of opening the plate. It is a MINIMUM
   now that the rake is declared — where the row is taller than this plate's
   content, .hp-text's auto margin opens the gap further, and on a warm plate
   that extra is colour rather than void. It still binds where the lead's
   sentence wraps longest: measured, the gap is exactly the declared figure at
   1200 and across the 1000–1180 band, and only opens past it above 1200. */
.sec-hardware-pieces .hp-card--lead .hp-mark { margin-bottom: var(--s96); }
/* Occlusion silhouettes use the GLOBAL .illo-mask, which paints itself in
   --illo-ground — declared beside each card variant's own background, so a mist
   plate masks in mist and the peach plate masks in peach with nothing local to
   drift. This file used to re-declare the class privately as `.hp-mask`, which
   is the one thing ILLUSTRATION.md item 5 forbids by name: the private class
   works here and silently fails (`fill: none`, i.e. an x-ray drawing) the
   moment anyone copies a path out of this file. */

/* ── Names ───────────────────────────────────────────────────────────
   All four are the system's card title. The lead's used to be Source Serif at
   26px against its own row-mates' Inter, which put two typefaces at one rank
   inside a single grid; it now takes `.card__title--lead` — same family, same
   weight, one size up.

   ONLY THE LEAD SPEAKS, and that is the hierarchy this file twice claimed and
   never executed. All four plates used to ship a two-line sentence, so the lead
   was marked out by a tint and 6px of title and nothing else: a recoloured
   sibling rather than a different rank. The three supporting plates carry a
   name, the lead carries a name AND the row's only sentence, and the peach is
   then confirming a rank the type has already set rather than inventing one.

   The sentence is the lead's alone, so it needs no colour rule: .card--peach
   already paints every p inside it sienna. A light-ground grey here would have
   fought that. */
.sec-hardware-pieces .hp-say {
  font-size: var(--t-body);
  line-height: 1.55;
  max-width: 28ch;
  text-wrap: pretty;
}
.sec-hardware-pieces .hp-card--lead .card__title { margin-bottom: var(--s12); }
/* A name with nothing under it must end where a name-plus-sentence ends: the
   component's own 10px bottom margin would otherwise push the three plain
   plates' only line 10px off the shared baseline the row is measured on. */
.sec-hardware-pieces .hp-card:not(.hp-card--lead) .card__title { margin-bottom: 0; }

/* Stagger the four draw-ins across the row. The mechanism is the shared
   [data-draw] primitive; only the timing is local. The duration is shortened
   from the primitive's 1600ms because the path carrying data-draw here is a
   whole device silhouette, not a short accent stroke: at 1600ms plus the
   stagger the last plate was still two thirds undrawn two seconds after the
   row appeared, which reads as a broken drawing, not as a drawing appearing. */
.sec-hardware-pieces [data-draw] { transition-duration: 820ms; }

/* THE DASH HAS TO BE RESTATED, and it is a real bug the primitive cannot see.
   `.illo` sets vector-effect:non-scaling-stroke so 1.4 holds at any size;
   Chrome then resolves stroke-dasharray in SCREEN space while 10-motion.js
   measures --len with getTotalLength(), which returns USER units. These
   drawings render at 264/146 = 1.81x, so a 1:1 dash is 45% short of the path
   it has to cover and the last third of every device outline never arrives.

   2.0 clears the widest breakpoint's 1.81 with headroom. The cost is that the
   stroke completes at half the transition's progress, which under the ease-out
   is a quick confident draw rather than a slow one — the right trade against a
   drawing that never finishes. */
.sec-hardware-pieces .illo [data-draw] {
  stroke-dasharray: calc(var(--len, 1000) * 2);
  stroke-dashoffset: calc(var(--len, 1000) * 2);
}
.sec-hardware-pieces .illo [data-draw].is-in { stroke-dashoffset: 0; }
.sec-hardware-pieces .hp-card:nth-child(1) [data-draw] { transition-delay: 40ms; }
.sec-hardware-pieces .hp-card:nth-child(2) [data-draw] { transition-delay: 130ms; }
.sec-hardware-pieces .hp-card:nth-child(3) [data-draw] { transition-delay: 220ms; }
.sec-hardware-pieces .hp-card:nth-child(4) [data-draw] { transition-delay: 310ms; }

/* ── Narrower ────────────────────────────────────────────────────── */
@media (max-width: 1180px) {
  .sec-hardware-pieces .hp-head { margin-bottom: var(--s96); }
  .sec-hardware-pieces .hp-row {
    --plate-fit: calc((min(var(--page), 100vw - var(--gutter) * 2) - var(--s12) * 3) / 4.26
                      - var(--s16) * 2 - 10px);
    --draw-w: min(var(--illo-card), var(--plate-fit));
    grid-template-columns: 1.26fr 1fr 1fr 1fr;
    gap: var(--s12);
  }
  .sec-hardware-pieces .hp-card--b { --hp-rake: 16px; }
  .sec-hardware-pieces .hp-card--c { --hp-rake: 32px; }
  .sec-hardware-pieces .hp-card--d { --hp-rake: 48px; }
  /* Same joint. The offset must cancel THIS breakpoint's card padding, which is
     still the component's s32 here — s24 only arrives with the two-up block
     below, and using it early put the plate edge 6 viewBox units off the
     monitor's corner at 1120 and 1000, i.e. back to a cut through the face. */
  .sec-hardware-pieces .hp-card--lead .hp-mark {
    margin-top: calc(var(--s32) * -1 - var(--draw-w) * var(--hp-cross));
    margin-bottom: var(--s80);
  }
  .sec-hardware-pieces .hp-card--lead .hp-n { top: var(--s16); right: var(--s24); }
  .sec-hardware-pieces .hp-n { right: var(--s16); }
}

@media (max-width: 999px) {
  /* Two up. The rake is a statement about a single row on one counter; the
     same offsets inside a 2x2 block just read as plates that failed to line
     up, so every plate goes back to one top padding and the pairs bottom-align
     on their own row. The lead plate keeps its colour, its larger name and the
     row's only sentence, and the terminal still crosses its own top edge at the
     same joint — the hierarchy survives the breakpoint because it never
     depended on size. */
  .sec-hardware-pieces .hp-head { margin-bottom: var(--s80); }
  .sec-hardware-pieces .hp-row {
    grid-template-columns: 1fr 1fr;
    gap: var(--s16);
    --plate-fit: calc((min(var(--page), 100vw - var(--gutter) * 2) - var(--s16)) / 2
                      - var(--s24) * 2 - 10px);
    --draw-w: min(var(--illo-card), var(--plate-fit));
  }
  .sec-hardware-pieces .hp-card { padding: var(--s24); }
  /* Two up: no rake. The step is a statement about one row on one counter and
     the same offsets inside a 2x2 read as plates that failed to line up; the
     pairs stretch and bottom-align on their own row instead. */
  .sec-hardware-pieces .hp-card { --hp-rake: 0px; }
  .sec-hardware-pieces .hp-card--lead .hp-mark {
    margin-top: calc(var(--s24) * -1 - var(--draw-w) * var(--hp-cross));
    margin-bottom: var(--s40);
  }
  .sec-hardware-pieces .hp-card--lead .hp-n { top: var(--s20); right: var(--s24); }
  .sec-hardware-pieces .hp-n { right: var(--s24); }
}

@media (max-width: 860px) {
  .sec-hardware-pieces .hp-head { grid-template-columns: 1fr; row-gap: var(--s16); }
  .sec-hardware-pieces .hp-eyebrow {
    grid-column: 1;
    grid-row: 1;
    justify-self: start;
    align-self: start;
    padding-bottom: 0;
    text-align: left;
  }
  .sec-hardware-pieces .hp-title { grid-row: 2; max-width: 20ch; }
}

/* ── Phone ───────────────────────────────────────────────────────────
   The three plain pieces close up into ONE ruled rack — hairlines between
   them, not gaps: three separate white boxes on a light ground at 390px is
   three times the same shape with nothing to distinguish them, and the plate
   edges carry no meaning the hairlines do not. The lead plate stays a warm
   block above the rack, which is the whole hierarchy in one screen.

   Each piece turns on its side, object left and name right, so the section is
   ~740px of rack instead of 1,600px of stacked box. That is not only a length
   preference: the page's grain overlay is viewport-fixed, so anything past
   844px lands on a measurably different surface in a full-page capture — one
   of four family members on its own ground. The section is built to end above
   that line. */
@media (max-width: 680px) {
  .sec-hardware-pieces .hp-head { margin-bottom: var(--s32); }
  .sec-hardware-pieces .hp-title { max-width: none; }
  .sec-hardware-pieces .hp-row {
    grid-template-columns: 1fr;
    gap: 0;
    --draw-w: 96px;
  }
  /* FLEX, NOT GRID, and that is the whole phone fix. Each piece was a two-row
     grid whose drawing spanned BOTH rows while `.card__title` also claimed
     `1 / span 2` — so the sentence was pushed into an implicit third row under
     the drawing, and the spanning drawing then stretched the two rows it
     covered until 84px of dead air sat between "Handheld." and its own
     sentence. Four plates each read as a name and an orphaned line, and the
     section measured 2,062px on a 390px screen.
     A row is now object | text, and the lead's name and sentence are ONE flex
     child (.hp-text) so nothing can put air between them again. */
  .sec-hardware-pieces .hp-card {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: var(--s16);
    border-radius: 0;
    padding: var(--s16) var(--s20) var(--s16) var(--s12);
  }
  .sec-hardware-pieces .hp-card { --hp-rake: 0px; }
  .sec-hardware-pieces .hp-card + .hp-card { border-top: 1px solid var(--hair); }
  .sec-hardware-pieces .hp-card--lead {
    border-radius: var(--r-card) var(--r-card) 0 0;
    padding: var(--s24) var(--s20) var(--s24) var(--s12);
  }
  /* Every text column in the section starts on ONE x — the lead plate's name
     and the three below it all begin at the same inset, so the rack has a
     spine instead of four independently indented labels. */
  .sec-hardware-pieces .hp-mark,
  .sec-hardware-pieces .hp-card--lead .hp-mark {
    flex: 0 0 var(--draw-w);
    margin: 0;
  }
  /* The rack turns the column on its side, so the text stops hanging off the
     bottom edge and centres against its object instead. */
  .sec-hardware-pieces .hp-text { flex: 1 1 auto; min-width: 0; margin-top: 0; }
  .sec-hardware-pieces .hp-say { font-size: 15px; max-width: none; }
  .sec-hardware-pieces .card__title { margin-bottom: var(--s4); }
  /* The register number gets its own band clear of the name: parked level with
     it, it collided with the second word of "Dual-screen terminal." and read
     as part of the sentence. */
  .sec-hardware-pieces .hp-n { top: var(--s8); right: var(--s16); font-size: 12px; }
  .sec-hardware-pieces .hp-card--lead .hp-n { top: var(--s12); right: var(--s16); }
}

/* ===== section:hardware-built ===== */
/* ============================================================================
   hardware-built — "Built for a working shop" + the pricing note.

   THE PAGE'S CODA, and deliberately the quietest thing on it. No fill, no
   card, no drawing, no slab: the whole construction is four rules, one serif
   sentence and the space between them.

   Four decisions carry it.

   1. TURNED NINETY DEGREES FROM THE SECTION ABOVE. hardware-pieces is a serif
      headline top-left over four illustrated cards running ACROSS the page,
      each a 20px title on a 16px slate body. Those four specs run DOWN a
      narrow column here, as run-in heads — the texture of a spec sheet rather
      than of a second card row. The serif crosses to the other side of the
      same axis.

   2. THE LABEL AND THE CAP ARE ONE MARK. An earlier build spent a whole row on
      a caption and a second row on a rule under it — two elements saying "this
      section starts here". They are now a masthead: the caption holds the page
      rail and the 2px rule leaves its right shoulder, crosses the trench, runs
      the width of the register and keeps going off the right of the screen. It
      is the register's first hairline AND the section's top edge, so the two
      columns interlock instead of merely sharing a top alignment.

   3. THE SERIF IS ANCHORED TO THE FLOOR. Its column is bottom-aligned, so its
      last baseline sits on the section's floor and the emptiness rises ABOVE
      it. That empty rectangle is bounded on every side — the caption at its
      top-left corner, the cap rule along its top, the page rail at its left,
      the register's spine at its right, the serif's own cap height underneath.
      A void with edges, not the leftover under a paragraph that stopped.

   4. ONE FOCAL POINT, AND IT IS THE PRICE. An earlier build made "Warranty."
      40px — twice every other title — which put the section's loudest word on
      its ONLY unconfirmed claim. Inverted here: the register runs 20/17/17/17
      and the statement is the only large type in the section.

   The page's peach is unspent and stays unspent. The single warm mark is the
   sienna proof query, which hangs in the trench as a margin note and is an
   editorial marker, not decoration.

   Every rule is scoped under .sec-hardware-built.
   ========================================================================== */

.sec-hardware-built {
  /* Distance from the right of the page measure to the right of the screen.
     The cap and the register rules are exactly this much longer than the
     boxes they belong to. */
  --hb-rail: max(var(--gutter), (100vw - var(--page)) / 2);
  /* The trench. It is not air: it is the margin-note column, and the note is
     set to exactly this width, so its full stop lands on the register's left
     edge and nothing it contains can ever reach across into the serif. 128px
     is the measured floor — "Confirm before" is ~104px at 14px italic, and
     anything narrower drops the note into three lines. */
  --hb-gap: var(--s128);
}

/* Two tracks on the page measure, two rows: masthead, then body.
   The serif column is the wider of the two so a 42px display line still gets
   ~31 characters while the 17px register stays near 60 — both at a real
   reading measure, which four narrow columns could never give either of them.

   Row 2 is sized by the register, which is much the taller of its two items;
   that is what gives the statement a floor to hang from. */
.sec-hardware-built .wrap {
  display: grid;
  grid-template-columns: minmax(0, 1.34fr) minmax(0, 1fr);
  column-gap: var(--hb-gap);
  grid-template-rows: max-content minmax(0, 1fr);
  align-items: start;
}

/* ——— The masthead ————————————————————————————————
   Caption and cap on one line. The rule is centred in the caption's line box,
   so it crosses at the label's mid-height and the two read as a single
   horizontal mark rather than as a label above a border. */
.sec-hardware-built .hb-mast {
  grid-column: 1 / span 2;
  grid-row: 1;
  display: flex;
  align-items: center;
  gap: var(--s24);
  /* Half the caption's leading already sits under the rule; the register's own
     top padding supplies the rest, so the first entry hangs the same distance
     below this rule as the others hang below theirs. */
  margin-bottom: var(--s8);
}

/* The container owns the gap under it — the one legal .eyebrow override. */
.sec-hardware-built .hb-eyebrow {
  margin-bottom: 0;
  flex: 0 0 auto;
}

/* Takes the rest of the line and overshoots to the right of the screen. 2px
   against the three hairlines below it: the stack has a start, and the start
   is a line that belongs to the whole section rather than to one column.
   The negative margin is what carries it past the page measure — a flex item
   may exceed its free space by exactly the margin it gives back. */
.sec-hardware-built .hb-cap {
  flex: 1 1 auto;
  display: block;
  height: 2px;
  margin-right: calc(-1 * (var(--hb-rail) + 2px));
  background: var(--hair-strong);
}

/* ——— The statement ——————————————————————————————
   Bottom of its track. Everything above it inside the left column is the
   void, and the void is the point. */
.sec-hardware-built .hb-stmt {
  grid-column: 1;
  grid-row: 2;
  align-self: end;
  /* Two and a half times the register's size, and the only serif here.
     Tracking tightens as size grows, which is the system's signature: the
     shared .h-sm-serif is -0.009em at 26px, this is -0.018em at 42px. The
     3.3vw slope is set so the first sentence holds TWO lines at every desktop
     width — at 3.5vw it broke to three between 900 and 1000px. */
  font-size: clamp(26px, 3.3vw, 42px);
  line-height: 1.2;
  letter-spacing: -0.018em;
  /* Caps the line at ~31 characters at every size, so the sentence keeps a
     display measure instead of running to the column edge on wide screens. */
  max-width: 31ch;
  /* Optical, not box, alignment: the serif's left sidebearing sets the
     sentence about a pixel inside the rail the caption starts on. */
  margin-left: -1px;
  padding-top: var(--s40);
}

/* One sentence per block, a third of a line apart. At 42px a line break is
   visible typography, so neither sentence may start in the middle of a line
   the other one began — and without the step the four lines set as one grey
   paragraph and the closing clause lost its landing. Filling rather than
   balancing: the 31ch cap already puts both of them on two lines at display
   size, and balance re-broke the second sentence into a stub over an overlong
   line once the phone measure took over. */
.sec-hardware-built .hb-stmt > span {
  display: block;
  text-wrap: pretty;
}
.sec-hardware-built .hb-stmt > span + span { margin-top: 0.3em; }
/* .h-sm-serif carries no italic rule of its own; this gets exactly one. */
.sec-hardware-built .hb-stmt em { font-style: italic; }

/* ——— The register ——————————————————————————————
   Second row of the right track: it hangs off the masthead rule, which is its
   own first hairline. */
.sec-hardware-built .hb-reg {
  grid-column: 2;
  grid-row: 2;
  list-style: none;
  margin: 0;
  padding: 0;
}

/* No min-height and no imposed pitch: each entry is its own text plus equal
   air, so the gaps between rules vary with what is in them. An even step
   would be a grid rotated ninety degrees. */
.sec-hardware-built .hb-e {
  position: relative;
  padding-top: var(--s16);
  padding-bottom: var(--s40);
}

/* The lead entry. A step larger than its three peers and given more air
   underneath, so the register descends from the cap instead of repeating.
   Its top padding is the short one because it hangs off the MASTHEAD, whose
   line box already contributes half a caption's leading under the rule — so
   the ink under the 2px rule sits the same distance below it as the ink under
   each hairline does. */
.sec-hardware-built .hb-e--lead {
  padding-top: var(--s8);
  padding-bottom: var(--s48);
}
.sec-hardware-built .hb-e--lead .hb-e__x {
  font-size: var(--t-body-lg);
  line-height: 1.45;
  letter-spacing: var(--tr-body-lg);
}

/* The hairlines. Own column plus the distance to the edge of the screen — the
   one measure in the section that is not a measure. data-bar grows them from
   the left, so the register draws itself outward and off the page; the delays
   match each entry's own reveal step, so line and words arrive together. */
.sec-hardware-built .hb-e__rule {
  position: absolute;
  top: 0;
  left: 0;
  display: block;
  width: calc(100% + var(--hb-rail) + 2px);
  height: 1px;
  background: var(--hair-strong);
  transition-delay: var(--hb-d, 0ms);
}
.sec-hardware-built .hb-e:nth-child(2) .hb-e__rule { --hb-d: 90ms; }
.sec-hardware-built .hb-e:nth-child(3) .hb-e__rule { --hb-d: 180ms; }
.sec-hardware-built .hb-e:nth-child(4) .hb-e__rule { --hb-d: 270ms; }

/* One paragraph per entry: the name runs in at ink weight and the sentence
   continues in slate on the same line. A card would stack them; a spec sheet
   does not. */
.sec-hardware-built .hb-e__x {
  font-size: var(--t-body);
  line-height: 1.5;
  color: var(--slate);
  text-wrap: pretty;
}
.sec-hardware-built .hb-e__x b {
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -0.006em;
}

/* ——— The proof query ————————————————————————————
   Out of the column entirely: it occupies the trench, right-aligned so its
   full stop sits on the register's left edge — the same origin every rule
   starts from. It is dropped one line, onto the empty second line of the
   entry it queries, so nothing sits beside it and the alignment stays exact.
   Indenting it INWARD, as this once did, made an editorial note read as a
   nested list item. */
.sec-hardware-built .hb-e--query {
  /* Room under the claim for the note to sit clear of it at ANY measure. On a
     narrow desktop the warranty line wraps to two, and a note pinned to the
     entry's second line ended up shoulder to shoulder with "local
     replacement." — same baseline, zero gap. Anchoring to the entry's floor
     instead means the wrap count can never bring them level. */
  padding-bottom: calc(var(--s40) + var(--s40));
}
.sec-hardware-built .flag {
  position: absolute;
  bottom: var(--s40);
  right: 100%;
  width: var(--hb-gap);
  font-size: 14px;
  line-height: 1.45;
  font-style: italic;
  text-align: right;
  color: var(--sienna);
}

/* ——— Narrow desktop ————————————————————————————
   The trench gives up 16px and not a pixel more: it is the note's measure, and
   below ~104px "Confirm before" stops fitting on one line. */
@media (max-width: 1080px) {
  .sec-hardware-built { --hb-gap: calc(var(--s96) + var(--s16)); }
}

/* ——— Tablet and phone ————————————————————————————
   One column, in document order: masthead, register, statement. The cap and
   the rules keep their bleed — they now run to the edge of the phone screen,
   which is the same idea at a smaller radius — and the statement lands last,
   which is where the copy always wanted it. */
@media (max-width: 900px) {
  .sec-hardware-built .wrap {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: max-content max-content max-content;
  }

  .sec-hardware-built .hb-mast { grid-column: 1; grid-row: 1; }
  .sec-hardware-built .hb-reg  { grid-column: 1; grid-row: 2; }
  .sec-hardware-built .hb-stmt { grid-column: 1; grid-row: 3; align-self: start; padding-top: var(--s64); }

  .sec-hardware-built .hb-cap     { margin-right: calc(-1 * (var(--gutter) + 2px)); }
  .sec-hardware-built .hb-e__rule { width: calc(100% + var(--gutter) + 2px); }

  /* No trench to hang in. The note goes back in flow but stays out of the
     sentence's own alignment — flush RIGHT under the claim, where a run-in
     continuation could never sit. The floor it was given to clear goes with
     the trench that made it necessary. */
  .sec-hardware-built .hb-e--query { padding-bottom: var(--s40); }
  .sec-hardware-built .flag {
    position: static;
    display: block;
    width: auto;
    margin-top: var(--s8);
  }
}

@media (max-width: 720px) {
  /* A phone measure puts every entry back to two lines, so the rows are tall
     again and want less air between them than a one-line tablet row did. */
  .sec-hardware-built .hb-e { padding-bottom: var(--s32); }
  .sec-hardware-built .hb-e--lead { padding-bottom: var(--s40); }
  .sec-hardware-built .hb-e:last-child { padding-bottom: 0; }
}

/* ===== section:cta-band ===== */
/* ————————————————————————————————————————————————
   cta-band — the closing punctuation, shared by all eight pages.

   Round 4. Round 3 lost on FINISH, not on idea: the two largest objects in the
   frame were hand-drawn placeholder-bar SVGs (a 264×426 field and a 290×214
   fragment), so a third of the composition read as content-TBD; every surface
   in the band sat between #FFF and #EAEAEA, so there was no mass anywhere; and
   ~550×290 of empty gradient sat between the headline's descender and the
   card's left edge. Three changes, in order of size:

   1. THE SLAB. The card is now the only dark mass in any page's closing band —
      var(--ink), white keys, paper values, no shadow (elevation is reserved for
      .artifact and this is not one). It is the "one genuinely heavy element"
      the band never had.
   2. REAL TYPE, NOT BARS. The menu inside it is SET: 29 priced rows under six
      small-caps category headers, prices on one shared tabular column per
      column of the menu. The kitchen ticket at the other corner is set the same
      way. Nothing in this section is a placeholder bar any more, and the slab
      earns its area with character density rather than filling it.
   3. INTERLOCK. The slab is taller (768px against a ~690px band) and pinned so
      its top rides ABOVE the headline's first line rather than starting below
      it, and the file tile is now a CHILD of the slab, hung across its left
      edge — so the overlap is measured from the slab and holds at every width.
      The hole in the middle is closed by the tile, not by moving the rail.

   The crop is still measured: the slab overhangs the right edge by 108px and
   its inner padding-right is that overhang plus 28px, so every price and every
   value ends 28px inside the cut and the edge falls through row whitespace.
   ———————————————————————————————————————————————— */
.sec-cta-band {
  padding-block: var(--section-y-loose);
  border-top: 1px solid var(--hair);
  /* Every object here is meant to be cut by these edges, and the bloom must
     never bleed onto whichever section happens to precede us. */
  overflow: hidden;
}

/* — The light —————————————————————————————————
   Recomposed for a left-anchored frame: the warm primary bloom sits behind the
   display head at 30/26 rather than on the axis, a lilac holds the object half,
   and a low warm one sits under the cut cards so the bottom of the band never
   flattens out. The cool --wash-4 stays dropped (it read as a blue-grey corner
   on --fog) and the component's paper-coloured bottom fade is removed — this
   band sits on --fog, so that fade would paint a white shelf across the close
   of every page. */
.sec-cta-band > .wash {
  inset: 0;
  opacity: 0.72;
  filter: blur(18px) saturate(106%);
  background:
    radial-gradient(38% 46% at 24% 20%, var(--wash-1) 0%, transparent 66%),
    radial-gradient(48% 54% at 58% 32%, var(--wash-2) 0%, transparent 70%),
    radial-gradient(40% 46% at 14% 76%, var(--wash-3) 0%, transparent 72%),
    radial-gradient(40% 46% at 88% 80%, var(--wash-1) 0%, transparent 72%);
}
.sec-cta-band > .wash::after { content: none; }

/* — Stacking ———————————————————————————————————
   Type above everything; the objects above the closing rule; the rule beneath
   both. That ordering is what lets the full-measure hairline disappear under
   the slab instead of drawing a grey line across it. */
.sec-cta-band .ctab__wrap { position: relative; z-index: 2; }
.sec-cta-band .ctab__footwrap { position: relative; z-index: 0; }

/* — The rail ———————————————————————————————— */
.sec-cta-band .ctab {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  text-align: left;
  max-width: 760px;
}

.sec-cta-band .ctab__h {
  /* SECTION-HEAD SCALE, NOT DISPLAY. This partial is byte-identical on all
     eight pages, so a display-scale sign-off made the last words on six of them
     the loudest words on the page — louder than the page's own H1. The site's
     one display is the home hero; every section headline, this one included,
     sits on .h. The measure still makes the two decks read as a natural break
     off the rail rather than as a balanced pair of centred lines. */
  font-size: var(--t-h);
  line-height: 1.16;
  letter-spacing: var(--tr-h);
  max-width: 15ch;
}
/* Give the clipped line masks room for the italic 'y' descender, then take the
   extra leading back so the two decks keep the rhythm .h sets for them. */
.sec-cta-band .ctab__h .line-mask { padding-bottom: 0.14em; margin-bottom: -0.14em; }

/* Dropped from --t-body-lg. At 20px the lede competed with the headline; at
   17px it becomes the quiet second voice, and at 44ch it sets three short lines
   against the rail instead of two long centred ones. */
/* The measure is also a CLEARANCE. The slab's left edge marches left as the
   viewport narrows (it is 53vw wide against a fixed 108px overhang) while the
   rail's text stays pinned at the gutter, so the lede is the line that would
   meet the file tile first. Every step below is set to keep ~40px of air
   between the end of this paragraph and the tile's send button. */
.sec-cta-band .ctab__sub {
  margin: var(--s24) 0 0;
  font-size: var(--t-body);
  line-height: 1.5;
  max-width: 40ch;
}

/* One filled pill and one plain link, both flush to the rail. */
.sec-cta-band .ctab__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s12) var(--s28);
  margin-top: var(--s40);
}
.sec-cta-band .ctab__wa {
  /* .alink is a text link; give it a real hit target anyway. */
  min-height: 44px;
  padding-inline: 4px;
}

/* — The close ————————————————————————————————
   Full page measure, flush left, with a 2px ink tab thickening its left end and
   the fine print hanging off exactly that end. */
.sec-cta-band .ctab__foot {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  margin-top: var(--s48);
}
.sec-cta-band .ctab__rule {
  position: relative;
  display: block;
  width: 100%;
  height: 1px;
  background: var(--hair-strong);
}
.sec-cta-band .ctab__rule::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  width: 76px;
  height: 2px;
  background: var(--ink);
}
.sec-cta-band .ctab__fine { margin-top: var(--s20); color: var(--ash); }

/* — The objects ———————————————————————————————
   Placed against the section, not the wrap, so their crops are measured from
   the viewport edge and stay identical at every desktop width. */
.sec-cta-band .ctab__stage {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
}

/* THE SLAB. Anchored to the BOTTOM, never the top: the shared shell gives a
   first-child section 44px of extra top padding, and bottom anchoring is the
   only way the crop and the offset against the type read identically on a real
   page and in the isolated preview. Its HEIGHT is declared rather than derived,
   because the menu inside deliberately overruns it — that overrun is what
   guarantees the slab is never a part-empty dark field at any width. */
.sec-cta-band .ctab__art {
  position: absolute;
  right: -108px;
  bottom: -130px;
  /* 53vw keeps the overhang constant while the slab narrows with the viewport,
     so it can never reach across into the rail. At 1440 the slab's left edge
     lands at x=788, ~200px off the headline's ragged right — the hole the last
     round left between the two masses was 550px wide. */
  width: min(760px, 53vw);
  height: 768px;
}
.sec-cta-band .ctab__card {
  /* Overhang + 28px, so the price column ends well inside the cut and the edge
     always falls through whitespace. */
  --crop: 136px;
  /* The one dark mass. No shadow: elevation belongs to .artifact, and a slab
     this heavy separates on value alone. */
  --slab-hair: color-mix(in srgb, var(--paper) 58%, transparent);
  height: 100%;
  background: var(--ink);
  color: var(--paper);
  border-radius: var(--r-card);
  text-align: left;
  overflow: hidden;
}

.sec-cta-band .ctab__card-top {
  display: flex;
  align-items: center;
  gap: var(--s16);
  padding: 24px var(--crop) 20px 30px;
  border-bottom: 1px solid var(--slab-hair);
}
.sec-cta-band .ctab__card-title {
  font-size: 15px;
  font-weight: 500;
  letter-spacing: -0.008em;
  color: var(--paper);
  white-space: nowrap;
}
/* The single confirmation beat in the whole band, so it is the highest-contrast
   thing in it: paper on ink, on the same flush right column as every value and
   every price below it. */
.sec-cta-band .ctab__chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
  height: 24px;
  padding-inline: 11px;
  border-radius: var(--r-pill);
  background: var(--paper);
  color: var(--ink);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: -0.004em;
  white-space: nowrap;
}

.sec-cta-band .ctab__list { list-style: none; margin: 0; padding: 0; }
.sec-cta-band .ctab__list li {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 13px var(--crop) 13px 30px;
  font-size: 13.5px;
  line-height: 1.3;
}
.sec-cta-band .ctab__list li + li { border-top: 1px solid var(--slab-hair); }
.sec-cta-band .ctab__k { color: var(--smoke); white-space: nowrap; }
/* One flush right edge shared by the chip, every value here and every price in
   the menu below — the column that makes the slab read as one object. */
.sec-cta-band .ctab__v {
  margin-left: auto;
  color: var(--paper);
  font-weight: 450;
  white-space: nowrap;
}

/* — The menu, set ————————————————————————————
   28 priced rows under six small-caps headers. It overruns the slab's declared
   height by ~70px at 1440 and by more as the columns narrow, so the band always
   cuts it mid-row rather than leaving dark air at the bottom. */
.sec-cta-band .ctab__menu {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  column-gap: 36px;
  border-top: 1px solid var(--slab-hair);
  padding: 24px var(--crop) 0 30px;
}
.sec-cta-band .ctab__cat {
  font-size: 10.5px;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--smoke);
  padding-bottom: 8px;
  border-bottom: 1px solid var(--slab-hair);
}
.sec-cta-band .ctab__rows { list-style: none; margin: 9px 0 22px; padding: 0; }
.sec-cta-band .ctab__rows li {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding-block: 4.5px;
  font-size: 12.5px;
  line-height: 1.34;
  letter-spacing: -0.002em;
  color: var(--paper);
}
.sec-cta-band .ctab__it { min-width: 0; }
/* The qualifier is part of the item name, not a second field — set in the same
   line at the same size, one step down in value. */
.sec-cta-band .ctab__it i { font-style: normal; color: var(--smoke); }
.sec-cta-band .ctab__pr { margin-left: auto; color: var(--paper); white-space: nowrap; }

/* — The file tile ————————————————————————————
   A CHILD of the slab wrapper, hung across the slab's left edge at a fixed
   offset, so the object-on-object overlap is measured from the slab and cannot
   drift into the rail as the viewport narrows. It is also what closes the
   ~550px hole that used to sit between the headline and the card. */
.sec-cta-band .ctab__art3 {
  position: absolute;
  /* Crosses the slab's edge by 30px — exactly the slab's own left padding, so
     the overlap occludes surface and never a glyph. Vertically it straddles the
     hairline between the fact table and the menu, so it cuts a structural line
     rather than a row of type. */
  left: -192px;
  top: 232px;
  width: 214px;
}
.sec-cta-band .ctab__tile {
  display: flex;
  align-items: center;
  gap: 11px;
  /* Raised white, ringed rather than shadowed — elevation is reserved for
     .artifact. Most of the tile lies on the pale gradient, where an unringed
     white card has no edge at all. */
  background: var(--raised);
  border: 1px solid var(--hair-strong);
  border-radius: var(--r-small);
  padding: 12px 14px;
  transform: rotate(-2.5deg);
}
.sec-cta-band .ctab__tile-ico {
  flex: none;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 11px;
  background: var(--mist);
  color: var(--slate);
}
.sec-cta-band .ctab__tile-txt { min-width: 0; }
.sec-cta-band .ctab__tile-name {
  display: block;
  font-size: 12.5px;
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -0.006em;
  color: var(--ink);
  white-space: nowrap;
}
.sec-cta-band .ctab__tile-meta {
  display: block;
  margin-top: 2px;
  font-size: 11px;
  line-height: 1.3;
  color: var(--slate);
  white-space: nowrap;
}
/* The second ink mass, hanging off the tile's left edge onto the gradient — a
   third overlap, at a third scale. */
.sec-cta-band .ctab__send {
  position: absolute;
  left: -19px;
  top: 12px;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: var(--r-pill);
  background: var(--ink);
  color: var(--paper);
}

/* — The ticket ————————————————————————————————
   What the built shop prints: light, flat, no elevation, cut by the OPPOSITE
   edge and by the bottom. It sits below every line of type, so it can never
   collide with the rail at any viewport width. */
.sec-cta-band .ctab__art2 {
  position: absolute;
  left: -60px;
  width: 400px;
  /* Height is declared so the standing part is EXACTLY 130px at every width.
     The band's bottom padding is a constant 160px, so the ticket's top edge is
     always 30px clear of the fine print however the type above it reflows —
     anchoring by content height alone let it climb over the closing line at
     1024. */
  height: 260px;
  bottom: -130px;
}
.sec-cta-band .ctab__slip {
  height: 100%;
  background: var(--mist);
  border: 1px solid var(--hair-strong);
  border-radius: var(--r-card);
  /* Left padding is deeper than the 60px crop, so the viewport edge cuts empty
     card and never a glyph — the object is cropped, the type is not. */
  padding: 20px 24px 0 88px;
  overflow: hidden;
  /* Pivoted on its top-right corner, so the rotation can only ever drop the
     ticket away from the fine print, never lift it into the text. */
  transform: rotate(-4deg);
  transform-origin: 100% 0;
}
.sec-cta-band .ctab__slip-top {
  display: flex;
  align-items: baseline;
  gap: 16px;
  padding-bottom: 9px;
  border-bottom: 1px solid var(--hair-strong);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--slate);
}
.sec-cta-band .ctab__slip-top span:last-child { margin-left: auto; }
.sec-cta-band .ctab__slip-rows { list-style: none; margin: 10px 0 0; padding: 0; }
.sec-cta-band .ctab__slip-rows li {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding-block: 5px;
  font-size: 12.5px;
  line-height: 1.34;
  letter-spacing: -0.002em;
  color: var(--ink);
  white-space: nowrap;
}
.sec-cta-band .ctab__q { flex: none; width: 12px; color: var(--slate); }
.sec-cta-band .ctab__slip-rows i { font-style: normal; color: var(--slate); }

/* — Narrower desktops —————————————————————————
   The objects are anchored to the viewport, so the crops hold all the way down;
   only the type measure and the menu's column count need to give. */
@media (max-width: 1180px) {
  .sec-cta-band .ctab { max-width: 620px; }
  .sec-cta-band .ctab__sub { max-width: 32ch; }
  .sec-cta-band .ctab__menu { column-gap: 28px; }
}
@media (max-width: 1080px) {
  .sec-cta-band .ctab { max-width: 500px; }
  .sec-cta-band .ctab__sub { max-width: 30ch; }
  .sec-cta-band .ctab__card { --crop: 126px; }
  .sec-cta-band .ctab__art3 { left: -166px; top: 206px; width: 196px; }
}

/* — Stacked ————————————————————————————————————
   Below 980 there is no room beside the rail, so the slab drops into flow
   between the CTA and the closing line. It is NOT a full-width block inside
   both gutters: it keeps its left edge on the rail, runs 24px past the right
   edge of the screen, and keeps the file tile lying across its top-left corner
   — the crop-and-overlap idea survives the breakpoint instead of being deleted
   at it. The stage is a sibling BETWEEN the two wraps in source for exactly
   this reason, so the page still ends on the closing line. */
@media (max-width: 980px) {
  .sec-cta-band .ctab { max-width: none; }
  .sec-cta-band .ctab__sub { max-width: 46ch; }

  .sec-cta-band .ctab__stage {
    position: relative;
    inset: auto;
    width: 100%;
  }
  .sec-cta-band .ctab__art {
    position: relative;
    inset: auto;
    width: auto;
    /* Tuned so the cut lands mid-row, never on a category header — an orphan
       header at the crop reads as a bug, a half-row reads as a crop. */
    height: 620px;
    /* Left edge on the rail; right edge 24px past the screen, cropped by the
       section's own overflow. */
    margin: var(--s64) -24px 0 var(--gutter);
    animation: none;
  }
  /* Room above the title for the tile to cross the corner without covering it. */
  .sec-cta-band .ctab__card { --crop: 48px; }
  .sec-cta-band .ctab__card-top { padding: 52px 48px 18px 26px; }
  .sec-cta-band .ctab__list li { padding: 13px 48px 13px 26px; }
  .sec-cta-band .ctab__menu {
    grid-template-columns: minmax(0, 1fr);
    padding: 22px 48px 0 26px;
  }
  .sec-cta-band .ctab__mcol:last-child { display: none; }

  .sec-cta-band .ctab__art3 {
    left: 10px;
    top: -30px;
    width: 200px;
    animation: none;
  }
  .sec-cta-band .ctab__art2 { display: none; }

  .sec-cta-band .ctab__foot { margin-top: var(--s64); }
}

/* — Phones ————————————————————————————————————
   The action row is capped to the measure of the copy above it, so it can never
   bulge wider than the sentence it belongs to. */
@media (max-width: 720px) {
  .sec-cta-band .ctab__h { max-width: 13ch; }
  .sec-cta-band .ctab__sub { margin-top: var(--s16); max-width: 32ch; }
  .sec-cta-band .ctab__actions {
    margin-top: var(--s32);
    max-width: 34ch;
    gap: var(--s8) var(--s20);
  }
  .sec-cta-band .ctab__art { margin-top: var(--s48); height: 480px; }
  .sec-cta-band .ctab__art3 { left: 6px; top: -28px; width: 184px; }
  .sec-cta-band .ctab__foot { margin-top: var(--s48); }
  .sec-cta-band .ctab__rule::before { width: 56px; }
}
