/*
 * Telyne — component system.
 * ---------------------------------------------------------------------------
 * Constructed from the patterns in DESIGN-BENCHMARK.md §9. No hex appears in
 * this file; every value is a token from tokens.css.
 *
 * The governing rule, taken from Pixel Data: bind a constraint on the component
 * once, centrally — never at each instance. Both of that project's worst layout
 * bugs were one-line fixes in a shared place after being chased instance by
 * instance first.
 */

/* ===========================================================================
 * 1. Base
 * ======================================================================== */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* The sticky header would otherwise cover the top of any anchored section. */
  scroll-padding-top: calc(var(--nav-h) + var(--space-4));
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--text-body);
  line-height: var(--leading-body);
  letter-spacing: var(--tracking-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* Tabular figures everywhere numbers appear, so a counter cannot reflow the
   * line around it while it runs. */
  font-variant-numeric: tabular-nums;
  /*
   * clip, not hidden. overflow-x: hidden makes body a scroll container, and a
   * scroll container between a sticky element and the viewport silently kills
   * position: sticky — which is why the journey panel stopped tracking while
   * its column height still measured correct. clip clips without establishing
   * a scroll container.
   */
  overflow-x: clip;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  margin: 0;
  font-weight: 700;
  letter-spacing: var(--tracking-heading);
  line-height: var(--leading-heading);
  /* A heading whose last word wraps alone reads as a mistake. Balancing the
   * lines fixes the whole class at once and degrades to ordinary wrapping. */
  text-wrap: balance;
}

p,
li,
dd {
  margin: 0;
  text-wrap: pretty;
}

ul,
ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

a {
  color: var(--link);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

img,
svg {
  max-width: 100%;
}

/* One visible focus treatment across the whole site: brand-500 at 3px with a
 * 2px offset (REVISION-1 §1). 5.10:1 against white, 4.72:1 against the alt
 * band — both clear the 3:1 minimum for a non-text indicator with room over. */
:focus-visible {
  outline: var(--focus-width) solid var(--focus-ring);
  outline-offset: var(--focus-offset);
  border-radius: var(--radius-sm);
}

::selection {
  background: var(--brand-600);
  color: var(--text-inverse);
}

/*
 * `sr-only`, anchored.
 *
 * An absolutely-positioned box with no `left` sits at its *static* position and
 * is only clipped by an ancestor that is its containing block — which a
 * `position: static` scroll wrapper is not. Inside a horizontally-scrolling
 * table that lets it extend the document sideways. Pixel Data measured 561px of
 * scrollWidth at a 430px viewport from exactly this. `left: 0` anchors it to
 * its containing block, which cannot be further right than the page.
 */
.sr-only {
  position: absolute;
  left: 0;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: var(--space-4);
  top: var(--space-4);
  z-index: 100;
  transform: translateY(-250%);
  background: var(--brand-600);
  color: var(--text-inverse);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-md);
  font-weight: 600;
  font-size: var(--text-sm);
  transition: transform var(--duration-fast) var(--ease-out);
}

.skip-link:focus {
  transform: translateY(0);
}

/*
 * Reduced motion — the hard stop. Repeated from tokens.css deliberately so that
 * anything added later cannot escape it, whether or not it uses the tokens.
 * Disabled, not shortened: everything renders in its finished state.
 */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ===========================================================================
 * 2. Layout — full-bleed backgrounds, constrained content
 * ======================================================================== */

/*
 * Two rules, without exception:
 *   1. Backgrounds are full-bleed. A tinted or dark section spans the whole
 *      viewport — no floating panels with dead gutters.
 *   2. Content is constrained. A 1900px measure is unreadable at any size.
 * The gutter is fluid rather than stepped, so it grows smoothly from a phone to
 * a 4K display instead of jumping at breakpoints.
 */
.container-page {
  width: 100%;
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 4vw, 3rem);
}

.container-prose {
  width: 100%;
  max-width: var(--container-prose);
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 4vw, 3rem);
}

/* ===========================================================================
 * SECTION RHYTHM — REVISION-2 §7.2
 *
 * Pixel Data alternates evenly: white, tint, white, tint, every band the full
 * width of the viewport with a hairline top and bottom. That metronome is one
 * of the things being recognised.
 *
 * Telyne varies deliberately instead. Four treatments, and the rule enforced by
 * qa-layout.js is that NO TWO ADJACENT SECTIONS SHARE ONE:
 *
 *   plain   — white, edge to edge, no rule
 *   wash    — a tint running full-bleed, no border, blending at its edges
 *   inset   — a rounded tinted panel floating on white with air around it
 *   deep    — the dark band
 *
 * `inset` is the one Pixel Data does not have at all, and it is what breaks the
 * metronome: the section stops being a stripe across the viewport and becomes
 * an object on the page.
 * ======================================================================== */

.band {
  width: 100%;
}

.band--plain {
  background: var(--bg);
}

/* Full-bleed tint with no hairline: the edges fade rather than being ruled. */
.band--wash {
  background: linear-gradient(
    180deg,
    var(--bg) 0%,
    var(--bg-alt) 12%,
    var(--bg-alt) 88%,
    var(--bg) 100%
  );
}

.band--wash-tint {
  background: linear-gradient(
    180deg,
    var(--bg) 0%,
    var(--tint-azure) 14%,
    var(--tint-azure) 86%,
    var(--bg) 100%
  );
}

/* A panel, not a stripe. Floats on white with its own radius and air. */
.band--inset {
  background: var(--bg);
  padding-block: var(--space-4);
}

.band--inset > .container-page {
  max-width: calc(var(--container-max) + 2 * clamp(1.25rem, 4vw, 3rem));
}

.band--inset .inset-panel {
  border-radius: var(--radius-xl);
  background: var(--bg-alt);
  padding: var(--space-10) clamp(1.25rem, 4vw, 3rem);
}

@media (min-width: 1024px) {
  .band--inset .inset-panel {
    padding-block: var(--space-16);
  }
}

.band--dark,
.band--deep {
  background: var(--ink-880);
  color: var(--text-inverse);
}

/*
 * On the dark band every text level and every card flips, in one place, rather
 * than each element carrying an override. A card here keeps the §7.1 rule — a
 * borderless tinted surface — but the tint is a white wash instead of a
 * capability tint, because a pale tint on near-black is not a tint, it is a
 * different card.
 */
.band--deep .label {
  color: var(--brand-300);
}

.band--deep .sec-title,
.band--deep .card-title,
.band--deep h2,
.band--deep h3 {
  color: var(--text-inverse);
}

.band--deep .sec-sub,
.band--deep .card-text,
.band--deep p {
  color: var(--on-dark-70);
}

.band--deep .note {
  color: var(--on-dark-45);
}

.band--deep .card {
  background: var(--on-dark-06);
}

.band--deep .card--interactive:hover {
  background: var(--on-dark-10);
}

.band--deep .link-arrow {
  color: var(--brand-300);
}

.band--deep :focus-visible {
  outline-color: var(--brand-300);
}

/*
 * Section rhythm — REVISION-3 §4.
 *
 * The gap a reader sees between two sections is the SUM of both paddings, not
 * one of them. At 64/80 per side that summed to 128px and 160px, which is the
 * dead white the revision describes: ten gaps over 96px, measured, every one
 * of them between a section that ended and the next that began.
 *
 * Each side now contributes half the intended gap, so two adjacent sections
 * total 64px at mobile and 80px from 1024 up — the rule as written.
 */
.sec {
  padding-block: var(--space-8);
}

.sec--narrow {
  padding-block: var(--space-6);
}

@media (min-width: 1024px) {
  .sec {
    padding-block: var(--space-10);
  }
}

/*
 * THE SECTION HEADER RULE — REVISION-6 §7
 *
 * Every content section carries an eyebrow (micro, uppercase, accent), a
 * heading (display-s) and a sub-line (lg, secondary, max two lines), with the
 * same 12px between each. A right-hand link only where there is somewhere to
 * go.
 *
 * ONE EXEMPTION, by decision rather than by oversight: the structural-facts
 * band. It is a figure strip, not a content section — five numbers and their
 * labels — and a header would defeat what it is. The gate encodes the
 * exemption so it is never reported as a failure.
 *
 * Section heading block (§7.3). Left-aligned against generous right
 * whitespace rather than centred — the heading occupies seven of twelve
 * columns and the remaining five are deliberately empty.
 */
.sec-head {
  max-width: 42rem;
}

/*
 * REVISION-8 §1 — headers are centred.
 *
 * Revision 7 made them span the container, which is not the same as balanced:
 * a left-aligned heading above a full-width grid still strands the right side.
 * Eyebrow, heading and sub-line all centre in a 720px block.
 *
 * Two exemptions: the hero, which is a split with the illustration, and the
 * facts band, which has no header at all.
 */
.sec-head {
  width: 100%;
  max-width: 720px;
  margin-inline: auto;
  text-align: center;
}

.label {
  display: block;
  font-size: var(--text-micro);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--eyebrow-text);
}

.sec-title {
  margin-top: var(--space-3);
  font-size: var(--text-display-s);
  font-weight: 700;
  letter-spacing: var(--tracking-display);
  line-height: 1.15;
}

.sec-sub {
  margin-top: var(--space-3);
  font-size: var(--text-lg);
  line-height: 1.5;
  color: var(--text-secondary);
  /* Measure, not column count. */
  max-width: 62ch;
  margin-inline: auto;
}

.sec-body {
  margin-top: var(--space-10);
}

.note {
  font-size: var(--text-xs);
  line-height: 1.6;
  color: var(--text-muted);
}

/* Grids. Card grids break at md, feature splits at lg, stat rows at sm.
 * `min-width: 0` on every child is what stops a long token widening a column
 * past the viewport — bound here once rather than per instance. */
.grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-5);
}

.grid > * {
  min-width: 0;
}

@media (min-width: 768px) {
  .grid--2,
  .grid--3 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 1024px) {
  .grid--3 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-10);
}

.split > * {
  min-width: 0;
}

/*
 * ASYMMETRIC SPLITS — REVISION-2 §7.3
 *
 * Pixel Data splits 6/6 and centres its section headings. Telyne uses 7/5 and
 * 5/7 on a twelve-column basis, and headings sit left against deliberate right
 * whitespace. Both collapse to one column below 1024 exactly as before.
 */
@media (min-width: 1024px) {
  .split {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .split--7-5 {
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  }

  .split--5-7 {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  }

  .split--center {
    align-items: center;
  }

  /*
   * Top-aligned, for a split whose two columns are different heights.
   * Centring a short column against a tall one opens a void above it — at
   * 1024px the code panel sat 150px below the cards beside it, so the left
   * half of the section read as empty.
   */
  .split--start {
    align-items: start;
  }
}

/* ===========================================================================
 * 3. Buttons
 * ======================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  /* 44, not 40. DESIGN-AUTHORITY §12: a control that behaves like a button
     carries the Apple HIG and WCAG AAA floor. Measured at 390 the three
     chrome buttons were 40px — four short — and nothing was asserting it. */
  height: 44px;
  padding-inline: var(--space-4);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 700;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  transition: background-color var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out);
}

.btn:hover {
  text-decoration: none;
}

.btn:disabled {
  opacity: 0.5;
  pointer-events: none;
}

.btn--lg {
  height: 48px;
  padding-inline: var(--space-6);
  font-size: var(--text-body);
  border-radius: var(--radius-lg);
}

.btn--sm {
  height: 32px;
  padding-inline: var(--space-3);
}

/* Hover goes darker, never lighter. Lightening a filled button lowers its
 * contrast at the exact moment the pointer is on it. */
/*
 * The primary button carries the one gradient (§1, §3): azure to cyan, sized
 * to twice the button so hovering shifts its position over 400ms rather than
 * changing colour. background-position is a paint-only property, so nothing
 * reflows.
 *
 * The flat brand-600 stays underneath as the background-color, which is what
 * the label's 6.90:1 contrast is measured against and what shows if a gradient
 * ever fails to paint.
 */
.btn--primary {
  background-color: var(--brand-600);
  background-image: var(--gradient-primary);
  background-size: 220% 100%;
  background-position: 0% 0;
  color: var(--text-inverse);
  box-shadow: var(--shadow-brand);
  transition: background-position var(--duration-fast) var(--ease-out),
    background-color var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-fast) var(--ease-out),
    transform var(--duration-fast) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .btn--primary:hover {
    background-position: 100% 0;
  }
}

.btn--primary:active {
  background-color: var(--brand-800);
  background-image: none;
}

@media (prefers-reduced-motion: reduce) {
  .btn--primary {
    transition: none;
  }
}

.btn--secondary {
  background: var(--surface);
  color: var(--text);
  border-color: var(--border-strong);
}

.btn--secondary:hover {
  background: var(--surface-hover);
}

.btn--secondary:active {
  border-color: var(--brand-600);
}

.btn--ghost {
  background: transparent;
  color: var(--text-secondary);
}

.btn--ghost:hover {
  background: var(--surface-hover);
  color: var(--text);
}

.btn--full {
  width: 100%;
}

.btn svg {
  flex: none;
}

/* A text link that behaves as an action. */
.link-arrow {
  display: inline-flex;
  align-items: center;
  /* §12: a list link is 24px. These measured 21. */
  min-height: 24px;
  gap: var(--space-2);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--link);
}

.link-arrow svg {
  flex: none;
  transition: transform var(--duration-fast) var(--ease-out);
}

.link-arrow:hover svg {
  transform: translateX(2px);
}

/* ===========================================================================
 * 4. Badges, eyebrows, chips
 * ======================================================================== */

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--ring-brand-30);
  background: var(--tint-brand-12);
  border-radius: var(--radius-pill);
  font-size: var(--text-micro);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--eyebrow-text);
}

/*
 * State chip. The tone arrives as --chip-fg / --chip-bg set on the element from
 * a token, never as a literal colour. One shape, four states.
 */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-05) var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--chip-bg, var(--surface-hover));
  color: var(--chip-fg, var(--text-secondary));
  font-size: var(--text-micro);
  font-weight: 600;
  line-height: 1.4;
  white-space: nowrap;
}

.dot {
  width: 6px;
  height: 6px;
  flex: none;
  border-radius: var(--radius-pill);
  background: currentColor;
}

/* ===========================================================================
 * 5. Card and icon chip
 * ======================================================================== */

/*
 * CARDS — REVISION-2 §7.1
 *
 * Pixel Data's card is a white surface with a 1px border and a soft shadow, at
 * rest, everywhere. Telyne's is a borderless tinted surface: it takes the faint
 * tint of its capability area, carries no outline, and gains a shadow only on
 * hover. --card-tint is set per instance from a token; the default is the
 * neutral wash.
 *
 * Where an edge is genuinely needed — to separate a card from a band of the
 * same tint — it is a 2px accent rule on the TOP EDGE ONLY (.card--ruled),
 * never a full outline.
 */
.card {
  border: 0;
  border-radius: var(--radius-lg);
  background: var(--card-tint, var(--bg-alt));
  padding: var(--space-5);
  min-width: 0;
}

.card--ruled {
  box-shadow: inset 0 2px 0 0 var(--chip-fg, var(--brand-500));
}

.card--interactive {
  transition: background-color var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-base) var(--ease-out),
    transform var(--duration-base) var(--ease-out);
}

.card-title {
  font-size: var(--text-h3);
  font-weight: 600;
  letter-spacing: var(--tracking-heading);
}

.card-text {
  margin-top: var(--space-2);
  font-size: var(--text-sm);
  line-height: 1.55;
  color: var(--text-secondary);
}

/* The capability mark: 40×40 tinted square with a coloured glyph. */
.icon-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  flex: none;
  border-radius: var(--radius-md);
  background: var(--chip-bg, var(--tint-azure));
  color: var(--chip-fg, var(--brand-600));
}

.icon-chip--sm {
  width: 28px;
  height: 28px;
  border-radius: var(--radius-sm);
}

/* ===========================================================================
 * 6. Form controls
 * ======================================================================== */

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

/*
 * 16px below `sm`, 13px from `sm` up.
 *
 * iOS Safari zooms the whole page when a focused input is under 16px and does
 * not zoom back out. `maximum-scale=1` would also stop it, by taking pinch-zoom
 * away from everybody — a WCAG failure and a hostile thing to do to someone who
 * needs to magnify text. The font size is the fix.
 */
.input {
  width: 100%;
  min-width: 0;
  max-width: 100%;
  height: 48px;
  padding-inline: var(--space-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  font-size: 1rem;
  transition: border-color var(--duration-fast) var(--ease-out);
}

@media (min-width: 640px) {
  .input {
    font-size: var(--text-sm);
  }
}

/* ---------------------------------------------------------------------------
 * .input--area — the one control that is not one line high
 *
 * .input sets `height: 48px`, which is right for every single-line field and
 * wrong for a textarea: it overrides rows="6" and collapses the message box to
 * the height of a text input. The contact page has carried this class since it
 * was written and nothing defined it, so the message field was a 48px slot
 * with six rows of text scrolling inside it.
 *
 * height:auto hands control back to rows, min-height keeps it useful if rows is
 * ever dropped, and resize is vertical only — a horizontally resizable control
 * can be dragged out of its column.
 * ------------------------------------------------------------------------ */
.input--area {
  height: auto;
  min-height: 9.5rem;
  padding-block: var(--space-3);
  line-height: 1.6;
  resize: vertical;
}

.input::placeholder {
  color: var(--text-muted);
}

.input:hover {
  border-color: var(--text-muted);
}

.input[aria-invalid="true"] {
  border-color: var(--danger-500);
}

.form-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  min-width: 0;
}

.form-row .field {
  flex: 1 1 16rem;
}

/* Errors name the field and the fix; they never blame the person typing. */
.form-msg {
  font-size: var(--text-xs);
  line-height: 1.5;
  min-height: 1.5em;
}

.form-msg[data-tone="error"] {
  color: var(--danger-600);
  font-weight: 500;
}

.form-msg[data-tone="ok"] {
  color: var(--success-700);
  font-weight: 500;
}

/* ===========================================================================
 * 7. Pre-launch notice — REVISION-2 §2
 *
 * Restored. Slim, one line, a tinted band with a link to the launch list. It
 * sits above the header and scrolls away; the header stays sticky beneath it.
 * Sized and coloured to read as candour rather than as an apology: no warning
 * icon, no amber, no border shouting at the top of the page.
 * ======================================================================== */

.notice {
  background: var(--tint-azure);
  color: var(--text-secondary);
  font-size: var(--text-xs);
  line-height: 1.5;
}

.notice-in {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-1) var(--space-3);
  padding-block: var(--space-2);
  text-align: center;
}

.notice b {
  color: var(--text);
  font-weight: 600;
}

.notice a {
  font-weight: 600;
  white-space: nowrap;
}

/* ===========================================================================
 * 8. Navigation
 * ======================================================================== */

/*
 * THE NAV — REVISION-4 §2.1
 *
 * The floating pill is gone. It was fighting the notice bar above it: two
 * stacked objects with different left edges, neither aligned to the content.
 * A full-width bar with the logo on the container edge is what the reference
 * uses and it aligns with the hero beneath it — which the gate now measures.
 *
 * On scroll past 16px it gains blur and a stronger shadow. Only paint
 * properties change, so the page never reflows as it sticks.
 */
.hdr {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  transition: box-shadow var(--duration-base) var(--ease-out),
    background-color var(--duration-base) var(--ease-out);
}

.hdr.stuck {
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: blur(14px);
  box-shadow: var(--shadow-md);
}

@media (prefers-reduced-motion: reduce) {
  .hdr {
    transition: none;
  }
}

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  height: var(--nav-h);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text);
  font-size: var(--text-h3);
  font-weight: 700;
  letter-spacing: var(--tracking-heading);
  flex: none;
}

.brand:hover {
  text-decoration: none;
  opacity: 0.85;
}

.brand svg {
  flex: none;
}

.nav-main {
  display: none;
  align-items: center;
  gap: var(--space-05);
  min-width: 0;
}

.nav-act {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: none;
}

.navlink {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3);
  border: 0;
  background: transparent;
  border-radius: var(--radius-md);
  color: var(--text-secondary);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--duration-fast) var(--ease-out),
    background-color var(--duration-fast) var(--ease-out);
}

.navlink:hover {
  color: var(--text);
  background: var(--surface-hover);
  text-decoration: none;
}

.navlink[aria-expanded="true"] {
  color: var(--text);
  background: var(--surface-hover);
}

.navlink svg {
  flex: none;
  transition: transform var(--duration-fast) var(--ease-out);
}

.navlink[aria-expanded="true"] svg {
  transform: rotate(180deg);
}

.nav-item {
  position: relative;
}

/* Dropdown / mega panel. Sized to its content, never to the viewport. */
.mega {
  position: absolute;
  top: calc(100% + var(--space-2));
  left: 0;
  z-index: 50;
  width: max-content;
  max-width: min(38rem, calc(100vw - var(--space-8)));
  padding: var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
}

/* Opens with an 8px rise and a fade over 220ms (§3). `display` cannot be
 * transitioned, so visibility carries the closed state and the panel is taken
 * out of the tab order with inert-like hiding by the script. */
.mega {
  display: block;
  visibility: hidden;
  opacity: 0;
  transform: translateY(-8px);
  pointer-events: none;
  transition: opacity var(--duration-base) var(--ease-out),
    transform var(--duration-base) var(--ease-out),
    visibility 0s linear var(--duration-base);
}

.mega.open .mega-grid a {
  opacity: 1;
  transform: none;
}

.mega.open {
  visibility: visible;
  opacity: 1;
  transform: none;
  pointer-events: auto;
  transition: opacity var(--duration-base) var(--ease-out),
    transform var(--duration-base) var(--ease-out),
    visibility 0s;
}

@media (prefers-reduced-motion: reduce) {
  .mega {
    transform: none;
    transition: none;
  }
}

.mega--right {
  left: auto;
  right: 0;
}

.mega-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-05);
}

@media (min-width: 1024px) {
  .mega--wide .mega-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.mega-grid a {
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity var(--duration-fast) var(--ease-out),
    transform var(--duration-fast) var(--ease-out),
    background-color var(--duration-fast) var(--ease-out);
  transition-delay: calc(var(--i, 0) * 30ms);
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  color: inherit;
  transition: background-color var(--duration-fast) var(--ease-out);
  min-width: 0;
}

.mega-grid a:hover {
  background: var(--surface-hover);
  text-decoration: none;
}

.mega-grid a > span:last-child {
  min-width: 0;
}

.mega-grid b {
  display: block;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text);
}

.mega-grid b + span {
  display: block;
  margin-top: var(--space-05);
  font-size: var(--text-xs);
  line-height: 1.45;
  color: var(--text-muted);
}

/* ---------------------------------------------------------------------------
 * The Products menu's columns.
 *
 * The grid used to hold two icon cards, one per area. It now holds one column
 * per area with every capability in it, because a menu that says "Connectivity"
 * and "Developer platform" tells a visitor nothing about the seventeen things
 * behind those words. Seventeen icon cards would be a panel taller than the
 * viewport it opens over, so the card shape goes and a list takes its place.
 *
 * Generated from capabilities.json — see generate.js navColumns. Every
 * hand-typed list on this site has drifted.
 * ------------------------------------------------------------------------ */
.mega-col {
  min-width: 0;
}

.mega-colh {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin: 0 0 var(--space-2);
  padding: 0 var(--space-3);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text);
}

.mega-colh a {
  color: inherit;
  text-decoration: none;
}

.mega-colh a:hover {
  color: var(--link);
}

/* The count is the data's, printed so the menu cannot disagree with the
 * catalogue about how many there are. */
.mega-coln {
  font-size: var(--text-xs);
  font-weight: 500;
  color: var(--text-muted);
}

.mega-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-05);
}

/* 24px is DESIGN-AUTHORITY §12's floor for a text link in a list, and these
 * are text links in a list. qa-tap measures them. */
.mega-list a {
  display: flex;
  align-items: center;
  min-height: 24px;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: var(--text-secondary);
  text-decoration: none;
}

.mega-list a:hover {
  background: var(--surface-hover);
  color: var(--text);
}

.mega-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-2);
  padding: var(--space-3);
  border-top: 1px solid var(--border);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.burger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 44 for the same reason as .btn — see DESIGN-AUTHORITY §12. */
  width: 44px;
  height: 44px;
  flex: none;
  border: 0;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--text);
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-out);
}

.burger:hover {
  background: var(--surface-hover);
}

/* Mobile drawer — the same information, as labelled groups. It hangs beneath
 * the pill as its own inset panel rather than as a full-width tray, so the
 * floating treatment holds while it is open. */
.drawer {
  display: none;
  width: 100%;
  max-width: calc(100% - 2 * var(--space-4));
  margin: var(--space-2) auto 0;
  max-height: calc(100vh - var(--nav-h) - var(--space-8));
  overflow-y: auto;
  overscroll-behavior: contain;
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
}

.drawer.open {
  display: block;
}

.hdr.stuck .drawer {
  max-width: 100%;
  margin-top: 0;
  border-radius: 0;
  box-shadow: 0 12px 24px -12px rgba(8, 25, 46, 0.18);
}

.drawer-in {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  padding: var(--space-6) var(--space-5);
}

.drawer-grp > .label {
  color: var(--text-muted);
}

.drawer-grp .mega-grid {
  margin-top: var(--space-2);
}

.drawer-links {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.drawer-links a {
  padding: var(--space-2) 0;
  font-size: var(--text-body);
  font-weight: 600;
  color: var(--text);
}

.drawer-cta {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding-top: var(--space-6);
  border-top: 1px solid var(--border);
}

@media (min-width: 1024px) {
  .nav-main {
    display: flex;
  }

  .burger {
    display: none;
  }

  .drawer,
  .drawer.open {
    display: none;
  }

  .nav-act .btn--secondary {
    display: inline-flex;
  }
}

/* Below 1024 the secondary action folds away, but the primary CTA never hides
 * behind the burger — a header offering only a menu icon has no next step. */
.nav-act .btn--secondary {
  display: none;
}

/*
 * The header CTA shortens on a narrow phone rather than disappearing.
 *
 * Two complete labels, one shown at a time — not one label with its tail in a
 * span. `.btn` is a flex row with an 8px gap, so a span inside it becomes a
 * second flex item and the gap lands on top of the space already in the text:
 * "Join  the launch list", with a visible double space.
 */
.lbl-short {
  display: none;
}

/*
 * 413px, remeasured for the floating pill.
 *
 * The header needs 302px for the wordmark, the full label and the burger. The
 * old full-width container left viewport minus 40px, so the full label fitted
 * from 342px up and the threshold was 359px. The pill (§7.5) costs more: 32px
 * of outer margin plus 40px of inner padding, leaving viewport minus 72px. The
 * gate caught the consequence — .nav-act escaping .nav by 20px at 360 and 5px
 * at 375 — which is what a structural change paying for itself looks like.
 */
@media (max-width: 413px) {
  .lbl-full {
    display: none;
  }

  .lbl-short {
    display: inline;
  }
}

/* ===========================================================================
 * 8. Footer
 * ======================================================================== */

.ftr {
  border-top: 1px solid var(--border);
  background: var(--bg-alt);
}

.ftr-in {
  padding-block: var(--space-12);
}

.ftr-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-10);
}

.ftr-grid > * {
  min-width: 0;
}

@media (min-width: 640px) {
  .ftr-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 1024px) {
  .ftr-grid {
    grid-template-columns: 1.5fr repeat(5, minmax(0, 1fr));
  }
}

.ftr-brand {
  max-width: 22rem;
}

.ftr-brand p {
  margin-top: var(--space-4);
  font-size: var(--text-sm);
  line-height: 1.6;
  color: var(--text-secondary);
}

.ftr-brand .note {
  margin-top: var(--space-5);
}

.ftr-col h2 {
  font-size: var(--text-micro);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--text-muted);
}

.ftr-col ul {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

.ftr-col a {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  transition: color var(--duration-fast) var(--ease-out);
}

.ftr-col a:hover {
  color: var(--text);
}

.ftr-legal {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin-top: var(--space-12);
  padding-top: var(--space-6);
  border-top: 1px solid var(--border);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.ftr-legal a {
  color: var(--text-muted);
}

.ftr-legal a:hover {
  color: var(--text);
}

.ftr-legal-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-6);
}

@media (min-width: 640px) {
  .ftr-legal {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
  }
}

.ftr-disc {
  margin-top: var(--space-6);
  padding-top: var(--space-6);
  border-top: 1px solid var(--border);
}

/* ===========================================================================
 * 9. Scroll reveal — 12px rise and fade, 220ms, once, never replayed
 * ======================================================================== */

.reveal {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity var(--duration-base) var(--ease-out),
    transform var(--duration-base) var(--ease-out);
}

.reveal.in {
  opacity: 1;
  transform: none;
}

/*
 * Staggered children (REVISION-1 §3): a section's children arrive ~70ms apart
 * rather than the whole block moving at once. The index is set on the element
 * by site.js, so the delay is one multiplication rather than a class per
 * position, and it applies to any container marked data-stagger.
 */
[data-stagger] > * {
  opacity: 0;
  transform: translateY(14px) scale(0.98);
  transition: opacity var(--duration-base) var(--ease-out),
    transform var(--duration-base) var(--ease-out);
  transition-delay: calc(var(--i, 0) * 50ms);
}

[data-stagger].in > * {
  opacity: 1;
  transform: none;
}

.no-js [data-stagger] > * {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  [data-stagger] > * {
    opacity: 1;
    transform: none;
    transition: none;
    transition-delay: 0s !important;
  }
}

/*
 * The word-by-word headline reveal. Words are wrapped by site.js so the markup
 * stays one readable sentence and assistive technology reads it as one, and
 * each span rises and untilts into place ~45ms after the one before.
 */
.word {
  display: inline-block;
  opacity: 0;
  transform: translateY(14px) rotate(-1.5deg);
  transition: opacity 420ms var(--ease-out), transform 420ms var(--ease-out);
  transition-delay: calc(var(--i, 0) * 50ms);
}

.word.in {
  opacity: 1;
  transform: none;
}

.no-js .word {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .word {
    opacity: 1;
    transform: none;
    transition: none;
    transition-delay: 0s !important;
  }
}

/*
 * Card hover: a 3px lift and a shadow step. Bound to a pointer that can
 * actually hover, so a tap on a phone never leaves a card stuck in its hover
 * state — which is what "nothing on touch devices" has to mean in practice.
 */
@media (hover: hover) and (pointer: fine) {
  .card--interactive:hover,
  .seg:hover,
  .tr:hover,
  .pm:hover {
    transform: translateY(-3px);
    box-shadow: var(--shadow-md);
  }
}

/* One card hover, everywhere: transform and shadow, 200ms. */
.card--interactive,
.seg,
.tr,
.pm {
  transition: transform var(--duration-base) var(--ease-out),
    box-shadow var(--duration-base) var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
  .card--interactive,
  .seg,
  .tr,
  .pm {
    transition: none;
  }

  .card--interactive:hover,
  .seg:hover,
  .tr:hover,
  .pm:hover {
    transform: none;
  }
}

/* If scripting never runs, nothing may stay invisible. */
.no-js .reveal {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* ===========================================================================
 * REVISION-4 §2.1 — bar layout, logo lockup, centred menu
 * ======================================================================== */

/*
 * Three columns rather than space-between: logo left, menu centred in the BAR
 * (not merely between its neighbours), actions right. With space-between the
 * menu's centre drifts as the two side groups change width, which is why the
 * reference's centred menu could not be reproduced by flex alone.
 */
.nav {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--space-4);
  height: var(--nav-h);
}

.nav-main {
  display: none;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-width: 0;
}

.navlink {
  padding: var(--space-2) var(--space-3);
}

/* The lockup: 29px mark, 10px gap, wordmark at 800 / −0.04em. */
.brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  flex: none;
  color: var(--text);
}

.brand:hover {
  text-decoration: none;
  opacity: 0.88;
}

.brand-logo {
  flex: none;
  display: block;
  /* Intrinsic width and height are on the element, so the ratio is locked
   * and the box is reserved before the image decodes. Height drives; width
   * follows from the artwork. */
  height: 28px;
  width: auto;
}

/* Below this the lockup can no longer sit beside the burger without
 * crowding it. The wordmark is still legible far smaller — its x-height
 * only reaches the 7px floor at a 58px lockup width — so this switch is
 * about horizontal room, not legibility. */
.brand-logo--mark {
  display: none;
  height: 28px;
}

@media (max-width: 359px) {
  .brand-logo {
    display: none;
  }

  .brand-logo--mark {
    display: block;
  }
}

.ftr .brand-logo,
.ftr .brand-logo--mark {
  height: 30px;
}

/* §2.5 — the bar collapses to logo, burger and the primary button at 1060. */
@media (min-width: 1060px) {
  .nav-main {
    display: flex;
  }

  .burger {
    display: none;
  }

  .drawer,
  .drawer.open {
    display: none;
  }
}

@media (max-width: 1059px) {
  .nav-main {
    display: none;
  }
}

/* ===================================================================
   Tap-target floor for text links — DESIGN-AUTHORITY §12
   ===================================================================
   The standard is two floors, not one: 44px for anything that behaves
   like a button, 24px for a text link in a list of links. Asserting 44
   everywhere failed thirty-six footer and nav links and would have meant
   a 500px footer column; asserting nothing left the floor unenforced.

   These are the four places a link sits in a list rather than in a
   sentence. min-height rather than padding on the block, so the text
   stays on its baseline and only the hit area grows. Inline links inside
   a paragraph are deliberately untouched — WCAG 2.2 exempts them, and
   padding them would break the line box they sit in.
   =================================================================== */
.ftr-col a,
.ftr-legal-links a,
.notice a,
.capture .note a {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
}

/* The sign-in link in the navigation's action area.
 *
 * A text link rather than a second button, because there is already one primary
 * action in that bar and check-routes.js has a rule about one action one label.
 * Signing in is something people go looking for; it does not need to compete
 * with the thing we are asking them to do.
 *
 * Hidden below the point where the bar gets crowded — the drawer carries it at
 * those widths, so it is never actually absent. The audit that preceded this
 * found ZERO routes to an account anywhere on the site, which is the fault this
 * closes; a link that disappeared on a phone would only half-close it.
 */
.navlink--signin {
  margin-right: var(--space-1);
}

@media (max-width: 860px) {
  .navlink--signin { display: none; }
}
