@charset "utf-8";
/* ==========================================================================
   Alarivean Design System
   Shared stylesheet for the Sophia Delta site network.
   Sixteen sites run the ocean palette and override only --accent-*. Sophia
   Delta is a second brand on this sheet and also overrides the eighteen
   surface, ink and type tokens listed in scripts/tokens.mjs. Nothing else in
   this file is a site's to move.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Faces
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Roboto";
  src: url("../fonts/roboto-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Roboto";
  src: url("../fonts/roboto-500.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Roboto";
  src: url("../fonts/roboto-700.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Roboto";
  src: url("../fonts/roboto-900.woff2") format("woff2");
  font-weight: 800 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Archivo";
  src: url("../fonts/archivo-500.woff2") format("woff2");
  font-weight: 400 600;
  font-style: normal;
  font-display: swap;
}

/* Sophia Delta's face. It ships in the shared sheet rather than in a second
   stylesheet because there is no second stylesheet — sixteen sites and one
   second brand run off this file, and a site that loaded its own would be the
   first crack in that. Nothing on the ocean sites names Montserrat, so nothing
   on them fetches it. */
@font-face {
  font-family: "Montserrat";
  src: url("../fonts/montserrat-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Montserrat";
  src: url("../fonts/montserrat-500.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Montserrat";
  src: url("../fonts/montserrat-600.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Montserrat";
  src: url("../fonts/montserrat-700.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
/* h1, h2, .display and .nav__brand-text all ask for 900, and that weight is
   written into the rules rather than into a token, so a site cannot lower it.
   Montserrat Black at 5.5rem is a solid block, and the brand deck sets the
   wordmark in Bold. Declaring ExtraBold across 800-900 answers the 900 request
   with the weight the deck actually uses and still leaves a real step above the
   700 that h3 and .btn carry. Same mechanism as the Roboto face above, used for
   the opposite purpose. */
@font-face {
  font-family: "Montserrat";
  src: url("../fonts/montserrat-800.woff2") format("woff2");
  font-weight: 800 900;
  font-style: normal;
  font-display: swap;
}

/* --------------------------------------------------------------------------
   2. Tokens
   -------------------------------------------------------------------------- */

:root {
  /* Ocean base */
  --abyss: #001324;
  --deep: #001b48;
  --ocean: #004581;
  --marine: #3086a6;
  --azure: #018abd;
  --teal: #009798;
  --sky: #97cbdc;
  --mist: #dde8f0;

  /* Warm accents */
  --gold: #fdd55b;
  /* .btn--gold:hover carried this as a literal, which meant the one button
     the build puts in every nav could not follow a site's palette. */
  --gold-hover: #f5c53a;
  --sand: #ead5b5;
  --ember: #d65200;

  /* Neutrals */
  --white: #ffffff;
  --foam: #f4f8fb;
  --slate-100: #e3ebf2;
  --slate-300: #b4c6d6;
  --slate-500: #6a8299;
  --slate-700: #33506b;

  /* Semantics */
  --text-strong: #001324;
  --text-body: #14283b;
  /* The brand book maps muted text to slate-500, which measures 3.73:1 on
     foam. Muted copy is still copy, so the semantic is pinned one step darker;
     --slate-500 remains available for rules, icons and non-text marks. */
  --text-muted: #5d7288;
  --text-inverse: var(--white);
  --text-inverse-muted: var(--sky);

  --brand: var(--marine);
  --brand-hover: #276f8a;
  --brand-active: #1f5a70;

  --status-good: var(--teal);
  --status-info: var(--azure);
  --status-warn: var(--gold);
  --status-danger: var(--ember);

  /* Per-site accent. Defaults to the house marine; system sites override.
     --accent      brand hue, for graphics and rules
     --accent-ink  text on light surfaces — must clear 4.5:1 on white
     --accent-cta  primary button fill
     --accent-on   text on --accent-cta — must clear 4.5:1 on it
     Every value in every site.json is checked by tests/contrast.test.mjs. */
  --accent: var(--marine);
  --accent-ink: #1f5a70;
  --accent-cta: #276f8a;
  --accent-soft: rgba(48, 134, 166, 0.12);
  --accent-on: var(--white);

  /* Lines */
  --line: rgba(0, 19, 36, 0.1);
  --line-2: rgba(0, 19, 36, 0.16);
  --line-3: rgba(0, 19, 36, 0.28);
  --line-dark: rgba(151, 203, 220, 0.24);
  /* Opaque, and it has to be. At 55% alpha this composited to about 2:1
     against every light surface in the palette, under the 3:1 WCAG 1.4.11 asks
     of a focus indicator, and an indicator nobody can see is not one. The
     selection wash below reads this same value at 22%, which is what the
     sixteen ocean sites always painted. */
  --focus: #018abd;

  /* Gradients */
  --grad-abyss: linear-gradient(180deg, #002a55, #001b48 55%, #001324);
  --grad-marine: linear-gradient(135deg, #018abd, #3086a6 50%, #004581);
  --grad-shallow: linear-gradient(180deg, #97cbdc, #3086a6);
  /* Not on the token whitelist and it does not need to be: the stops read
     --abyss, which is, so a second brand repaints the top of every hero by
     moving one value it already owns. */
  /* Starts at 30%, not 0%. Measured across every hero image where the text
     actually sits: white headline and body copy clear WCAG AA on all eighteen
     with room to spare, but the eyebrow sits in the band at the top where this
     gradient used to start from nothing, and failed 4.5:1 on fifteen sites.
     A 30% floor fixes four of them alone and lifts the worst headline pixel
     from 4.80:1 to 7.28:1. The eyebrow rule below does the rest. */
  --grad-protect: linear-gradient(
    180deg,
    color-mix(in srgb, var(--abyss) 30%, transparent),
    color-mix(in srgb, var(--abyss) 85%, transparent)
  );

  /* Type. Roboto Black carries display and site branding; the brand book's
     Viafont is retired from the web build because every site now has either a
     wordmark of its own or the Alarivean mark beside its name. */
  --f-display: "Roboto", system-ui, -apple-system, "Segoe UI", sans-serif;
  --f-sans: "Roboto", system-ui, -apple-system, "Segoe UI", sans-serif;
  --f-tech: "Archivo", "Roboto", system-ui, sans-serif;

  --t-2xs: 0.6875rem;
  --t-xs: 0.75rem;
  --t-sm: 0.875rem;
  --t-base: 1rem;
  --t-md: 1.125rem;
  --t-lg: 1.375rem;
  --t-xl: 1.75rem;
  --t-2xl: 2.25rem;
  --t-3xl: 3rem;
  --t-4xl: 4rem;
  --t-5xl: 5.5rem;

  --lead-tight: 1.05;
  --lead-snug: 1.2;
  --lead-normal: 1.5;
  --lead-relaxed: 1.65;
  --track-tight: -0.02em;
  --track-wide: 0.04em;
  --track-eyebrow: 0.16em;

  /* Fluid headline steps — clamp keeps long headlines honest on small screens */
  --h1: clamp(2.25rem, 1.35rem + 3.9vw, 4rem);
  --h1-lg: clamp(2.5rem, 1.2rem + 5.6vw, 5.5rem);
  --h2: clamp(1.75rem, 1.25rem + 2.1vw, 3rem);
  --h3: clamp(1.375rem, 1.12rem + 1.05vw, 2.25rem);
  --h4: clamp(1.125rem, 1.02rem + 0.42vw, 1.375rem);

  /* Space — 4px grid */
  --s-1: 0.25rem;
  --s-2: 0.5rem;
  --s-3: 0.75rem;
  --s-4: 1rem;
  --s-6: 1.5rem;
  --s-8: 2rem;
  --s-12: 3rem;
  --s-16: 4rem;
  --s-24: 6rem;
  --s-32: 8rem;

  --container: 1200px;
  --container-wide: 1400px;
  --container-read: 44rem;
  --gutter: 2rem;

  --r-card: 18px;
  --r-btn: 12px;
  --r-pill: 999px;

  --sh-md: 0 8px 24px rgba(0, 19, 36, 0.12);
  --sh-lg: 0 18px 48px rgba(0, 19, 36, 0.18);
  --sh-inset-dark: inset 0 1px 0 rgba(151, 203, 220, 0.12);

  --e-out: cubic-bezier(0.22, 1, 0.36, 1);
  --e-swell: cubic-bezier(0.37, 0, 0.15, 1);
  --d-1: 140ms;
  --d-2: 240ms;
  --d-3: 420ms;
  --d-4: 800ms;

  --nav-h: 5rem;
}

@media (max-width: 48rem) {
  :root {
    --gutter: 1.25rem;
    --nav-h: 4.25rem;
  }
}

/* --------------------------------------------------------------------------
   3. Reset & base
   -------------------------------------------------------------------------- */

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

/* The `hidden` attribute has to outrank a class that sets `display`. The browser's own
   `[hidden] { display: none }` is a single attribute selector, so `.btn { display:
   inline-flex }` beat it and every button this network hides in script - Apply and Clear on
   the media filter among them - kept its box and its place in the flex row while being
   invisible. `!important` is the normalize-era answer and it is the right one: `hidden`
   means hidden, and anything that needs to be laid out while hidden should not be using
   the attribute. */
[hidden] {
  display: none !important;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--nav-h) + 1rem);
}

body {
  margin: 0;
  background: var(--foam);
  color: var(--text-body);
  font-family: var(--f-sans);
  font-size: var(--t-base);
  line-height: var(--lead-relaxed);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  margin: 0 0 var(--s-4);
  color: var(--text-strong);
  font-weight: 700;
  line-height: var(--lead-snug);
  letter-spacing: var(--track-tight);
  text-wrap: balance;
}

h1 {
  font-size: var(--h1);
  line-height: var(--lead-tight);
  font-weight: 900;
}
h2 {
  font-size: var(--h2);
  line-height: var(--lead-snug);
  font-weight: 900;
}
h3 {
  font-size: var(--h3);
}
h4 {
  font-size: var(--h4);
}

p {
  margin: 0 0 var(--s-4);
  text-wrap: pretty;
}

a {
  color: var(--accent-ink);
  /* The underline follows the link ink. It used to be a fixed navy at 28%,
     which on a terracotta or magenta link read as two colors stacked, and
     measured fainter against every surface in the network than this does. */
  text-decoration-color: color-mix(in srgb, currentColor 45%, transparent);
  text-underline-offset: 0.18em;
  text-decoration-thickness: 0.06em;
  transition: color var(--d-1) var(--e-out), text-decoration-color var(--d-1) var(--e-out);
}
a:hover {
  color: var(--brand-hover);
  text-decoration-color: currentColor;
}

img,
svg,
video {
  max-width: 100%;
  height: auto;
  display: block;
}

/* Every photograph on the network is emitted as <picture class="x-media">.
   When it carries an aspect-ratio the frame crops; otherwise it flows. */
.x-media {
  display: block;
}
.x-media[style*="aspect-ratio"] {
  overflow: hidden;
}
.x-media[style*="aspect-ratio"] > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

ul,
ol {
  margin: 0 0 var(--s-4);
  padding-left: 1.25em;
}
li {
  margin-bottom: var(--s-2);
}

strong,
b {
  font-weight: 700;
  color: var(--text-strong);
}
/* Bold copy inherits the darkest ink, which disappears on the ocean surfaces.
   Every dark context has to pull it back to white. */
.on-dark strong,
.on-dark b,
.section--abyss strong,
.section--abyss b,
.section--deep strong,
.section--deep b,
.cta strong,
.cta b,
.hero strong,
.hero b,
.footer strong,
.footer b {
  color: var(--white);
}

small {
  font-size: var(--t-sm);
}

hr {
  border: 0;
  border-top: 1px solid var(--line);
  margin: var(--s-12) 0;
}

blockquote {
  margin: 0;
}

table {
  border-collapse: collapse;
  width: 100%;
}

button {
  font: inherit;
  color: inherit;
}

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

/* The one wash a reader paints themselves, by triple-clicking a paragraph to
   lift it into a memo. It reads --focus rather than a literal so the highlight
   and the focus ring cannot drift apart — they are one interaction family — and
   22% of an opaque --focus is exactly the rgba(1, 138, 189, 0.22) the sixteen
   ocean sites have always shown. */
::selection {
  background: color-mix(in srgb, var(--focus) 22%, transparent);
  color: var(--text-strong);
}
/* Inside a dark band the rule above puts the darkest ink in the palette on a
   barely-lightened dark surface — about 1.2:1, which is to say the selected
   text disappears at the moment someone tries to copy it. The ink inverts with
   the band. */
.on-dark ::selection,
.section--abyss ::selection,
.section--deep ::selection,
.hero ::selection,
.cta ::selection,
.nav ::selection,
.footer ::selection {
  color: var(--white);
}

.skip-link {
  position: absolute;
  left: var(--s-4);
  top: var(--s-4);
  z-index: 999;
  transform: translateY(-160%);
  background: var(--white);
  color: var(--text-strong);
  padding: var(--s-3) var(--s-6);
  border-radius: var(--r-btn);
  font-weight: 700;
  box-shadow: var(--sh-lg);
  transition: transform var(--d-2) var(--e-out);
}
.skip-link:focus {
  transform: translateY(0);
}

.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------------------------
   4. Layout
   -------------------------------------------------------------------------- */

.wrap {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.wrap--wide {
  max-width: var(--container-wide);
}
.wrap--read {
  max-width: var(--container-read);
}

.section {
  padding-block: clamp(3.5rem, 2rem + 6vw, 8rem);
  position: relative;
}
.section--tight {
  padding-block: clamp(2.5rem, 1.6rem + 3.6vw, 5rem);
}
.section--flush-top {
  padding-top: 0;
}

.section--foam {
  background: var(--foam);
}
.section--white {
  background: var(--white);
}
.section--mist {
  background: var(--mist);
}

.section--abyss {
  background: var(--grad-abyss);
  color: var(--text-inverse);
}
.section--deep {
  background: var(--deep);
  color: var(--text-inverse);
}

.section--abyss h1,
.section--abyss h2,
.section--abyss h3,
.section--abyss h4,
.section--deep h1,
.section--deep h2,
.section--deep h3,
.section--deep h4,
.on-dark h1,
.on-dark h2,
.on-dark h3,
.on-dark h4 {
  color: var(--text-inverse);
}
.section--abyss a:not(.btn),
.section--deep a:not(.btn),
.on-dark a:not(.btn),
.cta a:not(.btn) {
  color: var(--sky);
}
.section--abyss a:not(.btn):hover,
.section--deep a:not(.btn):hover,
.on-dark a:not(.btn):hover,
.cta a:not(.btn):hover {
  color: var(--white);
}

/* Grids */
.grid {
  display: grid;
  gap: var(--s-8);
}
.grid--2 {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.grid--3 {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.grid--4 {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

/* Editorial split: a narrow standfirst column beside a wider body column. */
.split {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: clamp(2rem, 1rem + 4vw, 5rem);
  align-items: start;
}
.split--even {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.split--wide-left {
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
}
.split--sticky > :first-child {
  position: sticky;
  top: calc(var(--nav-h) + var(--s-8));
}
/* A two-column grid inside a split column is already down to a fraction of a
   fraction. On the structure page that produced panels 293px wide and 1042px
   tall — a column of two-word lines. Inside a split, a grid gets one column and
   the cells keep the width the split gave them. */
.split .grid--2,
.split .grid--3,
.split .grid--4 {
  grid-template-columns: minmax(0, 1fr);
}

@media (max-width: 62rem) {
  .grid--4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .grid--3 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .split,
  .split--wide-left,
  .split--even {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s-8);
  }
  .split--sticky > :first-child {
    position: static;
  }
}

@media (max-width: 40rem) {
  .grid {
    gap: var(--s-6);
  }
  .grid--2,
  .grid--3,
  .grid--4 {
    grid-template-columns: minmax(0, 1fr);
  }
  .grid--keep-2 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* --------------------------------------------------------------------------
   5. Type helpers
   -------------------------------------------------------------------------- */

/* The eyebrow names what the section is about, and the headline under it is a
   promise about that subject rather than a restatement of it — so a reader who
   misses the eyebrow reads the whole section without knowing its topic. At
   twelve pixels and 600 against a headline that clamps up to 64, that is what
   was happening: the label was legible on inspection and invisible in the
   scroll. It is now a step larger, a step heavier, and carries a short rule
   that gives the eye something to catch before the type. The rule is
   currentColor, so it follows the accent on light sections and the pale ink on
   dark ones without a second declaration. */
.eyebrow {
  display: block;
  font-family: var(--f-tech);
  font-size: var(--t-sm);
  font-weight: 700;
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--accent-ink);
  margin-bottom: var(--s-4);
}
.eyebrow::before {
  content: "";
  display: inline-block;
  width: 1.5rem;
  height: 2px;
  margin-right: 0.625rem;
  vertical-align: 0.28em;
  background: currentColor;
}
@media (max-width: 48rem) {
  .eyebrow::before {
    width: 1rem;
    margin-right: 0.5rem;
  }
}
.on-dark .eyebrow,
.section--abyss .eyebrow,
.section--deep .eyebrow,
.cta .eyebrow {
  color: var(--sky);
}
/* The hero eyebrow is the one element on the first screen that failed AA -
   sky ink over a photograph, in the band where the scrim is thinnest, on
   fifteen of eighteen sites, as low as 2.18:1. Sky is right on a flat dark
   band and stays there; over an image it needs white. With the scrim floor
   above, the worst hero eyebrow measures 6.21:1 and none fails. It is also the
   line a first-time visitor is meant to read first, and it was the faintest
   thing on the screen. */
.hero .eyebrow {
  color: var(--white);
}

.display {
  font-family: var(--f-display);
  font-weight: 900;
  letter-spacing: var(--track-tight);
  line-height: var(--lead-tight);
}

.lede {
  font-size: clamp(1.0625rem, 1rem + 0.4vw, 1.375rem);
  line-height: var(--lead-relaxed);
  color: var(--slate-700);
  max-width: 40ch;
}
.on-dark .lede,
.section--abyss .lede,
.section--deep .lede {
  color: var(--sky);
}
.lede--wide {
  max-width: 56ch;
}

.muted {
  color: var(--text-muted);
}
.on-dark .muted,
.section--abyss .muted,
.section--deep .muted,
.cta .muted {
  color: color-mix(in srgb, var(--sky) 85%, transparent);
}

.tech {
  font-family: var(--f-tech);
  letter-spacing: var(--track-wide);
}

.prose > * + h2 {
  margin-top: var(--s-12);
}
.prose > * + h3 {
  margin-top: var(--s-8);
}
.prose p {
  max-width: 68ch;
}
.prose li {
  max-width: 66ch;
}
.prose h2 {
  font-size: var(--h3);
}
.prose h3 {
  font-size: var(--t-lg);
  font-weight: 700;
}

/* --------------------------------------------------------------------------
   6. Buttons & chips
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  padding: 0.875rem 1.75rem;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  font-weight: 700;
  font-size: var(--t-base);
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--d-2) var(--e-out), color var(--d-2) var(--e-out),
    border-color var(--d-2) var(--e-out), box-shadow var(--d-2) var(--e-out),
    transform var(--d-1) var(--e-out);
}
.btn:active {
  transform: scale(0.98);
}

.btn--primary {
  background: var(--accent-cta);
  color: var(--accent-on);
  box-shadow: 0 6px 20px rgba(0, 19, 36, 0.18);
}
.btn--primary:hover {
  background: var(--accent-ink);
  color: var(--accent-on);
  box-shadow: 0 10px 28px rgba(0, 19, 36, 0.24);
}

.btn--gold {
  background: var(--gold);
  color: var(--abyss);
  box-shadow: 0 6px 24px color-mix(in srgb, var(--gold) 32%, transparent);
}
.btn--gold:hover {
  background: var(--gold-hover);
  color: var(--abyss);
  box-shadow: 0 10px 32px color-mix(in srgb, var(--gold) 42%, transparent);
}

.btn--ghost {
  background: transparent;
  color: var(--text-strong);
  border-color: var(--line-3);
}
.btn--ghost:hover {
  background: rgba(0, 19, 36, 0.04);
  color: var(--text-strong);
  border-color: var(--text-strong);
}

/* `.cta` sets --grad-abyss and white text like the other dark surfaces, but was
   never added here, so a ghost button inside one rendered ink-on-ink and was
   effectively invisible on the lakes, rivers and oceans pages. Any dark
   surface that inverts its text has to invert its outline buttons too. */
.on-dark .btn--ghost,
.section--abyss .btn--ghost,
.section--deep .btn--ghost,
.cta .btn--ghost,
.hero .btn--ghost {
  color: var(--white);
  border-color: rgba(255, 255, 255, 0.4);
}
.on-dark .btn--ghost:hover,
.section--abyss .btn--ghost:hover,
.section--deep .btn--ghost:hover,
.cta .btn--ghost:hover,
.hero .btn--ghost:hover {
  background: rgba(255, 255, 255, 0.1);
  border-color: var(--white);
  color: var(--white);
}

.btn--sm {
  padding: 0.5rem 1.125rem;
  font-size: var(--t-sm);
}
.btn--block {
  width: 100%;
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: center;
}

/* A quiet inline link that still reads as an action */
.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  font-weight: 700;
  text-decoration: none;
  border-bottom: 1px solid currentColor;
  padding-bottom: 1px;
}
.link-arrow svg {
  transition: transform var(--d-2) var(--e-out);
}
.link-arrow:hover svg {
  transform: translateX(3px);
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  padding: 0.3rem 0.75rem;
  border-radius: var(--r-pill);
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-family: var(--f-tech);
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
}
.on-dark .chip,
.section--abyss .chip,
.section--deep .chip {
  background: rgba(151, 203, 220, 0.14);
  color: var(--sky);
}
.chip--warn {
  background: rgba(214, 82, 0, 0.1);
  color: #a63f00;
}
.chip--good {
  background: rgba(0, 151, 152, 0.12);
  color: #00696a;
}

.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  list-style: none;
  padding: 0;
  margin: 0;
}
.chip-row li {
  margin: 0;
}

/* --------------------------------------------------------------------------
   7. Navigation
   -------------------------------------------------------------------------- */

.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  background: color-mix(in srgb, var(--abyss) 72%, transparent);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border-bottom: 1px solid rgba(151, 203, 220, 0.16);
  transition: background var(--d-3) var(--e-out), border-color var(--d-3) var(--e-out);
}
.nav.is-scrolled {
  background: color-mix(in srgb, var(--abyss) 94%, transparent);
}

.nav__inner {
  width: 100%;
  max-width: var(--container-wide);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex;
  align-items: center;
  gap: var(--s-8);
}

.nav__brand {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  text-decoration: none;
  flex-shrink: 0;
  /* The wordmark is shorter than a comfortable tap target on small screens. */
  min-height: 44px;
  padding-right: var(--s-2);
}
.nav__brand img,
.nav__brand svg {
  height: 2.75rem;
  width: auto;
}
@media (max-width: 48rem) {
  .nav__brand img,
  .nav__brand svg {
    height: 2.25rem;
  }
}
.nav__brand-text {
  font-family: var(--f-display);
  font-weight: 900;
  font-size: var(--t-md);
  color: var(--white);
  letter-spacing: -0.005em;
  line-height: 1;
  white-space: nowrap;
}
/* Sites without their own wordmark set their name beside the Alarivean mark. */
.nav__brand-mark {
  height: 2.125rem;
  width: auto;
  flex-shrink: 0;
}
@media (max-width: 48rem) {
  .nav__brand-mark {
    height: 1.875rem;
  }
  .nav__brand-text {
    font-size: var(--t-base);
  }
}
.footer__brand .nav__brand-mark {
  height: 2.5rem;
  margin-bottom: 0;
}
.footer__brand-lockup {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: var(--s-6);
}
.footer__brand-lockup .nav__brand-text {
  font-size: var(--t-lg);
}

.nav__links {
  display: flex;
  align-items: center;
  gap: var(--s-6);
  margin: 0 0 0 auto;
  padding: 0;
  list-style: none;
}
.nav__links li {
  margin: 0;
}
/* :not(.btn) because the mobile menu carries the nav's call to action as a
   `.btn--gold` inside this list, and a bare `.nav__links a` outranks
   `.btn--gold`. That handed the one contact button a phone visitor can reach
   white-at-82% ink on the gold fill — 1.4:1, unreadable — along with the link
   padding and the link underline. Every declaration here is for a text link. */
.nav__links a:not(.btn) {
  color: rgba(255, 255, 255, 0.82);
  text-decoration: none;
  font-size: var(--t-sm);
  font-weight: 500;
  letter-spacing: 0.01em;
  padding-block: 0.4rem;
  border-bottom: 2px solid transparent;
  transition: color var(--d-1) var(--e-out), border-color var(--d-1) var(--e-out);
}
.nav__links a:not(.btn):hover,
.nav__links a:not(.btn)[aria-current="page"] {
  color: var(--white);
  border-bottom-color: var(--gold);
}

.nav__cta {
  flex-shrink: 0;
}

.nav__toggle {
  display: none;
  background: none;
  border: 1px solid rgba(151, 203, 220, 0.3);
  border-radius: 10px;
  width: 2.75rem;
  height: 2.75rem;
  margin-left: auto;
  cursor: pointer;
  align-items: center;
  justify-content: center;
  color: var(--white);
}
.nav__toggle span,
.nav__toggle span::before,
.nav__toggle span::after {
  display: block;
  width: 18px;
  height: 2px;
  background: currentColor;
  border-radius: 2px;
  transition: transform var(--d-2) var(--e-out), opacity var(--d-1) var(--e-out);
}
.nav__toggle span::before,
.nav__toggle span::after {
  content: "";
  position: absolute;
}
.nav__toggle span::before {
  transform: translateY(-6px);
}
.nav__toggle span::after {
  transform: translateY(6px);
}
.nav__toggle[aria-expanded="true"] span {
  background: transparent;
}
.nav__toggle[aria-expanded="true"] span::before {
  transform: rotate(45deg);
}
.nav__toggle[aria-expanded="true"] span::after {
  transform: rotate(-45deg);
}

@media (max-width: 60rem) {
  .nav__toggle {
    display: inline-flex;
    position: relative;
  }
  .nav__inner {
    gap: var(--s-4);
  }
  .nav__cta {
    display: none;
  }
  .nav__links {
    position: fixed;
    inset: var(--nav-h) 0 auto 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    /* Opaque, and no backdrop-filter, which did nothing behind it anyway. At
       98% the bleed is three levels per channel — invisible on body copy and
       plainly readable through an 88px hero headline, so the open menu looked
       like a broken overlay with "Who is behind the proposal on your desk"
       running underneath it. */
    background: var(--abyss);
    padding: var(--s-2) var(--gutter) var(--s-8);
    border-bottom: 1px solid rgba(151, 203, 220, 0.16);
    max-height: calc(100dvh - var(--nav-h));
    overflow-y: auto;
    transform: translateY(-8px);
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--d-2) var(--e-out), transform var(--d-2) var(--e-out),
      visibility var(--d-2);
  }
  .nav__links.is-open {
    transform: translateY(0);
    opacity: 1;
    visibility: visible;
  }
  .nav__links a:not(.btn) {
    display: block;
    padding: var(--s-4) 0;
    font-size: var(--t-md);
    border-bottom: 1px solid rgba(151, 203, 220, 0.14);
  }
  .nav__links a:not(.btn):hover,
  .nav__links a:not(.btn)[aria-current="page"] {
    border-bottom-color: rgba(151, 203, 220, 0.14);
    color: var(--gold);
  }
  /* The list's link rule used to hand this button its height by accident,
     along with the ink that made it unreadable. Scoping that rule to
     :not(.btn) took both away, so the CTA states its own size here. It is the
     only route to contact anywhere in the mobile chrome and has to be a thumb
     target — e2e/responsive.spec.mjs holds it to 40px. */
  .nav__links .btn {
    margin-top: var(--s-6);
    width: 100%;
    padding-block: var(--s-4);
    font-size: var(--t-base);
  }
}

body.nav-open {
  overflow: hidden;
}

/* --------------------------------------------------------------------------
   8. Hero
   -------------------------------------------------------------------------- */

.hero {
  position: relative;
  display: flex;
  align-items: flex-end;
  min-height: clamp(34rem, 30rem + 24vh, 46rem);
  padding-top: calc(var(--nav-h) + 4rem);
  padding-bottom: clamp(3rem, 2rem + 5vw, 6rem);
  background: var(--abyss);
  color: var(--text-inverse);
  overflow: hidden;
  isolation: isolate;
}
.hero--short {
  min-height: clamp(24rem, 20rem + 16vh, 32rem);
}
.hero--tall {
  min-height: clamp(38rem, 32rem + 30vh, 54rem);
}

.hero__media {
  position: absolute;
  inset: 0;
  z-index: -2;
}
.hero__media .x-media {
  width: 100%;
  height: 100%;
}
.hero__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--grad-protect);
}
/* A second, gentler left-weighted wash keeps headline contrast honest on
   bright plates without resorting to a solid black box. */
.hero__scrim::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    100deg,
    color-mix(in srgb, var(--abyss) 78%, transparent) 0%,
    color-mix(in srgb, var(--abyss) 42%, transparent) 42%,
    color-mix(in srgb, var(--abyss) 8%, transparent) 78%
  );
}

.hero__inner {
  position: relative;
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.hero__body {
  position: relative;
  max-width: 46rem;
}
/* Luminance contrast passed everywhere; texture did not. On reefhealer the
   sea fan behind the headline changes tone every few pixels - a third of
   neighboring samples differ by a visible step - and letterforms on a field
   like that are hard to read at any contrast ratio. A six-pixel blur behind
   the text takes that to zero on every hero and darkens nothing. No tint, no
   edge: the mask fades it out so it never reads as a card. */
.hero__body::before {
  content: "";
  position: absolute;
  inset: -1.5rem -1.25rem;
  z-index: -1;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  mask-image: radial-gradient(ellipse at center, #000 55%, transparent 100%);
  -webkit-mask-image: radial-gradient(ellipse at center, #000 55%, transparent 100%);
  pointer-events: none;
}
.hero h1 {
  color: var(--white);
  font-size: var(--h1-lg);
  text-shadow: 0 2px 24px rgba(0, 19, 36, 0.4);
}
.hero .lede {
  color: rgba(255, 255, 255, 0.9);
  max-width: 46ch;
  font-size: clamp(1.0625rem, 0.98rem + 0.5vw, 1.375rem);
}
.hero .btn-row {
  margin-top: var(--s-8);
}

.hero__foot {
  margin-top: clamp(2.5rem, 1.5rem + 4vw, 4.5rem);
  padding-top: var(--s-6);
  border-top: 1px solid rgba(151, 203, 220, 0.24);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-8) var(--s-12);
}
.hero__foot > * {
  flex: 1 1 11rem;
  min-width: 0;
}
/* Hero metrics live inside a 46rem column, not a full section, so the section
   metric size overflows them. A value like "100,000 cells/L" needs the smaller
   step or it forces the row to wrap two-plus-one. */
.hero__foot .metric__value {
  font-size: clamp(1.375rem, 1.05rem + 1.3vw, 2.25rem);
}

.hero--split .hero__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.85fr);
  gap: clamp(2rem, 1rem + 4vw, 5rem);
  align-items: end;
}
@media (max-width: 62rem) {
  .hero--split .hero__inner {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* A split hero gives the headline about sixty percent of the width a full-bleed
   one does, but --h1-lg scales on the viewport rather than on the column. It
   arrives at the same 88px and spends four lines on a nine-word sentence. Step
   it down while the two-column layout is actually in force; below the
   breakpoint the column is full width again and the standard size is right. */
@media (min-width: 62.0625rem) {
  .hero--split h1 {
    font-size: clamp(2.25rem, 1.35rem + 3.1vw, 3.5rem);
  }
}

/* --------------------------------------------------------------------------
   8b. Console figure — VaporFleet's hero
   --------------------------------------------------------------------------
   A drawn console rather than a photographed one.

   The figure carries layout and nothing else: a rail, a map panel, telemetry
   cards, a status bar. No text, no values, and no coastline that resolves to
   anywhere. It is a diagram of an interface and it is captioned as one, because
   a picture that reads as a screenshot and is not one would be its own small
   lie.

   Colors come through tokens rather than as literals in the markup, so a site
   that repaints its palette repaints the figure with it.

   Why it is a diagram at all is in AUTHORING.md, not here: this file ships to
   the browser with its comments intact.
   -------------------------------------------------------------------------- */

.console-figure {
  margin: 0;
  max-width: 34rem;
  margin-inline-start: auto;
}
.console-figure svg {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--r-card);
  box-shadow: 0 24px 64px rgba(0, 19, 36, 0.55);
}
.console-figure figcaption {
  margin-top: var(--s-3);
  color: rgba(255, 255, 255, 0.7);
  font-family: var(--f-tech);
  font-size: 0.8125rem;
  line-height: 1.45;
}

/* Surfaces */
.console-figure .cf-shell { fill: var(--abyss); }
.console-figure .cf-bar { fill: color-mix(in srgb, var(--deep) 70%, var(--abyss)); }
.console-figure .cf-rail { fill: color-mix(in srgb, var(--abyss) 80%, #000); }
.console-figure .cf-panel { fill: var(--deep); }
.console-figure .cf-card { fill: color-mix(in srgb, var(--deep) 78%, var(--abyss)); }

/* Hairlines and edges */
.console-figure .cf-line {
  fill: none;
  stroke: var(--sky);
  stroke-opacity: 0.16;
  stroke-width: 1;
}

/* Wireframe mass — stands in for interface furniture, never for words */
.console-figure .cf-mass { fill: var(--sky); fill-opacity: 0.22; }
.console-figure .cf-mass--dim { fill: var(--sky); fill-opacity: 0.11; }
.console-figure .cf-track { fill: var(--sky); fill-opacity: 0.12; }

/* Signal colors, straight off the palette */
.console-figure .cf-accent { fill: var(--accent); }
.console-figure .cf-teal { fill: var(--teal); }
.console-figure .cf-gold { fill: var(--gold); }
.console-figure .cf-ember { fill: var(--ember); }
.console-figure .cf-fill { fill: var(--azure); fill-opacity: 0.85; }

/* Map furniture */
.console-figure .cf-land { fill: var(--sand); fill-opacity: 0.16; }
.console-figure .cf-shallow { fill: var(--marine); fill-opacity: 0.16; }
.console-figure .cf-route {
  fill: none;
  stroke: var(--gold);
  stroke-opacity: 0.55;
  stroke-width: 1.5;
  stroke-dasharray: 5 5;
  stroke-linecap: round;
}
.console-figure .cf-spark {
  fill: none;
  stroke: var(--sky);
  stroke-opacity: 0.75;
  stroke-width: 1.5;
  stroke-linejoin: round;
  stroke-linecap: round;
}
.console-figure .cf-cta { fill: var(--teal); fill-opacity: 0.9; }

@media (max-width: 62rem) {
  .console-figure {
    margin-block-start: var(--s-8);
    margin-inline: 0;
    max-width: 30rem;
  }
}

/* --------------------------------------------------------------------------
   9. Cards & surfaces
   -------------------------------------------------------------------------- */

.card {
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  padding: clamp(1.5rem, 1.1rem + 1.4vw, 2.25rem);
  box-shadow: var(--sh-md);
  transition: box-shadow var(--d-2) var(--e-out), transform var(--d-2) var(--e-out);
}
.card > :last-child {
  margin-bottom: 0;
}
.card h3,
.card h2 {
  font-size: var(--t-lg);
}

a.card,
.card--link {
  display: block;
  text-decoration: none;
  color: inherit;
}
a.card:hover,
.card--link:hover {
  box-shadow: var(--sh-lg);
  transform: translateY(-3px);
}

.on-dark .card,
.section--abyss .card,
.section--deep .card {
  background: color-mix(in srgb, var(--ocean) 28%, transparent);
  border-color: var(--line-dark);
  box-shadow: var(--sh-inset-dark);
  color: rgba(255, 255, 255, 0.88);
}
.on-dark .card h3,
.on-dark .card h2,
.section--abyss .card h3,
.section--abyss .card h2,
.section--deep .card h3,
.section--deep .card h2 {
  color: var(--white);
}

.card__icon {
  color: var(--accent);
  margin-bottom: var(--s-4);
}
.on-dark .card__icon,
.section--abyss .card__icon,
.section--deep .card__icon {
  color: var(--sky);
}

.card__media {
  margin: calc(-1 * clamp(1.5rem, 1.1rem + 1.4vw, 2.25rem));
  margin-bottom: var(--s-6);
  border-radius: var(--r-card) var(--r-card) 0 0;
  overflow: hidden;
  aspect-ratio: 16 / 10;
}
.card__media .x-media {
  width: 100%;
  height: 100%;
}
.card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--d-4) var(--e-swell);
}
a.card:hover .card__media .x-media {
  width: 100%;
  height: 100%;
}
.card__media img {
  transform: scale(1.04);
}

/* A plain panel: no shadow, hairline only. Used where cards would shout. */
.panel {
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  padding: clamp(1.5rem, 1.1rem + 1.4vw, 2.5rem);
}
.panel > :last-child {
  margin-bottom: 0;
}
.on-dark .panel,
.section--abyss .panel,
.section--deep .panel {
  background: color-mix(in srgb, var(--deep) 50%, transparent);
  border-color: var(--line-dark);
}

/* --------------------------------------------------------------------------
   10. Metrics
   -------------------------------------------------------------------------- */

.metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: var(--s-8);
}
.metric__value {
  display: block;
  font-family: var(--f-tech);
  font-size: clamp(2rem, 1.4rem + 2.4vw, 3rem);
  font-weight: 600;
  line-height: 1;
  letter-spacing: var(--track-tight);
  color: var(--accent-ink);
  margin-bottom: var(--s-2);
}
.on-dark .metric__value,
.section--abyss .metric__value,
.section--deep .metric__value,
.hero .metric__value {
  color: var(--gold);
}
.metric__label {
  display: block;
  font-size: var(--t-sm);
  line-height: var(--lead-normal);
  color: var(--text-muted);
  max-width: 26ch;
}
.on-dark .metric__label,
.section--abyss .metric__label,
.section--deep .metric__label,
.hero .metric__label {
  color: var(--sky);
}
.metric__source {
  display: block;
  margin-top: var(--s-2);
  /* 11px is in the type scale but a source credit is a link, and an 11px link
     is not a link anyone over forty is going to hit. */
  font-size: var(--t-xs);
  color: var(--text-muted);
  letter-spacing: 0.02em;
}
.hero .metric__source,
.on-dark .metric__source {
  color: color-mix(in srgb, var(--sky) 70%, transparent);
}
.metric__source a {
  color: inherit;
}

/* Metrics laid out as a bordered strip rather than floating numbers */
.metrics--ruled {
  gap: 0;
}
.metrics--ruled > * {
  padding: var(--s-6) var(--s-8);
  border-left: 1px solid var(--line);
}
.metrics--ruled > *:first-child {
  padding-left: 0;
  border-left: 0;
}
.on-dark .metrics--ruled > *,
.section--abyss .metrics--ruled > * {
  border-left-color: var(--line-dark);
}
@media (max-width: 62rem) {
  .metrics--ruled > * {
    border-left: 0;
    border-top: 1px solid var(--line);
    padding: var(--s-6) 0;
  }
  .metrics--ruled > *:first-child {
    border-top: 0;
    padding-top: 0;
  }
  .on-dark .metrics--ruled > *,
  .section--abyss .metrics--ruled > * {
    border-top-color: var(--line-dark);
  }
}

/* --------------------------------------------------------------------------
   11. Editorial furniture
   -------------------------------------------------------------------------- */

.pullquote {
  margin: var(--s-12) 0;
  padding-left: clamp(1.25rem, 0.6rem + 2vw, 2.5rem);
  border-left: 3px solid var(--accent);
  font-size: clamp(1.25rem, 1.05rem + 0.9vw, 1.75rem);
  line-height: var(--lead-snug);
  font-weight: 500;
  color: var(--text-strong);
  letter-spacing: var(--track-tight);
}
.on-dark .pullquote,
.section--abyss .pullquote,
.section--deep .pullquote {
  border-left-color: var(--gold);
  color: var(--white);
}
.pullquote cite {
  display: block;
  margin-top: var(--s-4);
  font-size: var(--t-sm);
  font-style: normal;
  font-weight: 400;
  letter-spacing: 0.01em;
  color: var(--text-muted);
}
.on-dark .pullquote cite,
.section--abyss .pullquote cite {
  color: var(--sky);
}

figure {
  margin: var(--s-8) 0;
}
figure img,
figure .x-media {
  border-radius: var(--r-card);
}
figcaption {
  margin-top: var(--s-3);
  font-size: var(--t-sm);
  line-height: var(--lead-normal);
  color: var(--text-muted);
  max-width: 60ch;
}
.on-dark figcaption,
.section--abyss figcaption,
.section--deep figcaption {
  color: var(--sky);
}

/* A dated, signed field note. Deliberately plain — it should read like
   something a person typed, not a designed component. */
.fieldnote {
  border-top: 2px solid var(--text-strong);
  padding-top: var(--s-4);
  max-width: 34rem;
}
.fieldnote__meta {
  font-family: var(--f-tech);
  font-size: var(--t-xs);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--s-3);
}
.fieldnote p {
  font-size: var(--t-md);
  line-height: var(--lead-relaxed);
}
.fieldnote__sign {
  font-size: var(--t-sm);
  color: var(--text-muted);
}
.on-dark .fieldnote,
.section--abyss .fieldnote,
.section--deep .fieldnote {
  border-top-color: var(--sky);
}
.on-dark .fieldnote__meta,
.section--abyss .fieldnote__meta,
.on-dark .fieldnote__sign,
.section--abyss .fieldnote__sign {
  color: var(--sky);
}

/* Numbered claim list — the brand's parallel-list device */
.claims {
  list-style: none;
  padding: 0;
  margin: 0;
  counter-reset: claim;
}
.claims > li {
  counter-increment: claim;
  position: relative;
  padding-left: 3.25rem;
  padding-bottom: var(--s-6);
  margin-bottom: var(--s-6);
  border-bottom: 1px solid var(--line);
}
.claims > li:last-child {
  border-bottom: 0;
  margin-bottom: 0;
  padding-bottom: 0;
}
.claims > li::before {
  content: counter(claim, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: 0.1em;
  font-family: var(--f-tech);
  font-size: var(--t-sm);
  font-weight: 600;
  letter-spacing: var(--track-wide);
  color: var(--accent);
}
.on-dark .claims > li,
.section--abyss .claims > li,
.section--deep .claims > li {
  border-bottom-color: var(--line-dark);
}
.on-dark .claims > li::before,
.section--abyss .claims > li::before,
.section--deep .claims > li::before {
  color: var(--gold);
}
.claims strong {
  display: block;
  font-size: var(--t-md);
  margin-bottom: var(--s-1);
}
.on-dark .claims strong,
.section--abyss .claims strong,
.section--deep .claims strong {
  color: var(--white);
}

/* "Does NOT" reassurance list */
.nots {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: var(--s-3);
}
.nots li {
  display: flex;
  gap: var(--s-3);
  align-items: flex-start;
  margin: 0;
  font-size: var(--t-md);
}
.nots svg {
  flex-shrink: 0;
  margin-top: 0.28em;
  color: var(--teal);
}
.on-dark .nots svg,
.section--abyss .nots svg,
.section--deep .nots svg {
  color: var(--sky);
}

/* --------------------------------------------------------------------------
   12. Tables
   -------------------------------------------------------------------------- */

.table-wrap {
  --table-surface: var(--white);
  --table-edge: var(--line-3);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: var(--s-8) 0;
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  /* A four-column table does not fit a phone and never will, so the fix is not
     to shrink it but to say out loud that it scrolls. Two of these layers are
     the surface color pinned to the content (`local`), two are the shadows
     they sit over (`scroll`). At either end the surface covers its shadow; as
     soon as the reader can scroll that way, the shadow appears. Before this a
     390px screen showed column one of two and nothing at all to suggest the
     answer column existed. */
  background-color: var(--table-surface);
  background-image: linear-gradient(to right, var(--table-surface), transparent),
    linear-gradient(to left, var(--table-surface), transparent),
    linear-gradient(to right, var(--table-edge), transparent),
    linear-gradient(to left, var(--table-edge), transparent);
  background-position: 0 0, 100% 0, 0 0, 100% 0;
  background-repeat: no-repeat;
  background-size: 2.5rem 100%, 2.5rem 100%, 1rem 100%, 1rem 100%;
  background-attachment: local, local, scroll, scroll;
}
.on-dark .table-wrap,
.section--abyss .table-wrap,
.section--deep .table-wrap {
  --table-surface: color-mix(in srgb, var(--deep) 45%, transparent);
  --table-edge: var(--line-dark);
  border-color: var(--line-dark);
}

.table {
  min-width: 34rem;
  font-size: var(--t-sm);
}
.table th,
.table td {
  padding: var(--s-4) var(--s-6);
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--line);
  line-height: var(--lead-normal);
}
.table thead th {
  font-family: var(--f-tech);
  font-size: var(--t-xs);
  text-transform: uppercase;
  letter-spacing: var(--track-wide);
  color: var(--text-muted);
  font-weight: 600;
  white-space: nowrap;
  border-bottom-color: var(--line-3);
}
.table tbody tr:last-child td {
  border-bottom: 0;
}
/* No nowrap. A row header is a phrase, not a label, and holding it on one line
   pushed a two-column table to 645px inside a 348px phone — 46% of it, the
   answer column, off the screen. Column heads still hold their line. */
.table tbody th {
  font-weight: 700;
  color: var(--text-strong);
}
.on-dark .table th,
.on-dark .table td,
.section--abyss .table th,
.section--abyss .table td,
.section--deep .table th,
.section--deep .table td {
  border-bottom-color: var(--line-dark);
  color: rgba(255, 255, 255, 0.86);
}
.on-dark .table thead th,
.section--abyss .table thead th,
.section--deep .table thead th {
  color: var(--sky);
}
.on-dark .table tbody th,
.section--abyss .table tbody th,
.section--deep .table tbody th {
  color: var(--white);
}

/* --------------------------------------------------------------------------
   13. Disclosure / FAQ
   -------------------------------------------------------------------------- */

.faq {
  border-top: 1px solid var(--line-2);
}
.faq details {
  border-bottom: 1px solid var(--line-2);
}
.faq summary {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-6);
  padding: var(--s-6) 0;
  cursor: pointer;
  list-style: none;
  font-size: var(--t-md);
  font-weight: 700;
  color: var(--text-strong);
  transition: color var(--d-1) var(--e-out);
}
.faq summary::-webkit-details-marker {
  display: none;
}
.faq summary:hover {
  color: var(--accent-ink);
}
.faq summary::after {
  content: "";
  flex-shrink: 0;
  width: 0.75rem;
  height: 0.75rem;
  margin-top: 0.45em;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
  transition: transform var(--d-2) var(--e-out);
}
.faq details[open] summary::after {
  transform: rotate(-135deg);
}
.faq details > div {
  padding-bottom: var(--s-6);
  max-width: 68ch;
}
.faq details > div > :last-child {
  margin-bottom: 0;
}
.on-dark .faq,
.section--abyss .faq,
.section--deep .faq {
  border-top-color: var(--line-dark);
}
.on-dark .faq details,
.section--abyss .faq details,
.section--deep .faq details {
  border-bottom-color: var(--line-dark);
}
.on-dark .faq summary,
.section--abyss .faq summary,
.section--deep .faq summary {
  color: var(--white);
}
.on-dark .faq summary:hover,
.section--abyss .faq summary:hover {
  color: var(--sky);
}

/* --------------------------------------------------------------------------
   14. Call to action band
   -------------------------------------------------------------------------- */

/* The marine gradient is too light under body-size text, so the CTA band runs
   on abyss by default. --grad-marine stays for graphics and rules. */
.cta {
  position: relative;
  overflow: hidden;
  background: var(--grad-abyss);
  color: var(--white);
  isolation: isolate;
}
.cta--ocean {
  background: linear-gradient(135deg, #004581, #013a68 55%, #001b48);
}
.cta__media {
  position: absolute;
  inset: 0;
  z-index: -2;
}
.cta__media .x-media {
  width: 100%;
  height: 100%;
}
.cta__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.cta__media + .cta__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    120deg,
    color-mix(in srgb, var(--abyss) 92%, transparent),
    color-mix(in srgb, var(--ocean) 72%, transparent)
  );
}
.cta h2 {
  color: var(--white);
  max-width: 22ch;
}
.cta .lede {
  color: rgba(255, 255, 255, 0.9);
  max-width: 52ch;
}
.cta__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: clamp(2rem, 1rem + 4vw, 4rem);
  align-items: center;
}
@media (max-width: 56rem) {
  .cta__grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* --------------------------------------------------------------------------
   15. Footer
   -------------------------------------------------------------------------- */

.footer {
  background: var(--abyss);
  color: rgba(255, 255, 255, 0.72);
  padding-block: clamp(3rem, 2rem + 4vw, 5rem) var(--s-8);
  font-size: var(--t-sm);
}
.footer a {
  color: rgba(255, 255, 255, 0.72);
  text-decoration: none;
}
.footer a:hover {
  color: var(--white);
  text-decoration: underline;
}
.footer__top {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) repeat(auto-fit, minmax(9rem, 1fr));
  gap: var(--s-8) var(--s-12);
  padding-bottom: var(--s-12);
  border-bottom: 1px solid rgba(151, 203, 220, 0.18);
}
.footer__brand img,
.footer__brand svg {
  height: 3.25rem;
  width: auto;
  margin-bottom: var(--s-6);
}
.footer__blurb {
  max-width: 34ch;
  line-height: var(--lead-relaxed);
}
.footer__label {
  color: var(--sky);
  font-family: var(--f-tech);
  font-size: var(--t-xs);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  font-weight: 600;
  margin-bottom: var(--s-4);
}
.footer ul {
  list-style: none;
  padding: 0;
  margin: 0;
}
.footer li {
  margin-bottom: var(--s-3);
}
.footer__net {
  padding-block: var(--s-8);
  border-bottom: 1px solid rgba(151, 203, 220, 0.18);
}
.footer__net .footer__label {
  margin-bottom: var(--s-3);
}
.footer__network li a {
  font-size: var(--t-xs);
  letter-spacing: 0.01em;
  color: rgba(255, 255, 255, 0.58);
}
.footer__network li:not(:last-child) a::after {
  content: " ·";
  color: rgba(151, 203, 220, 0.35);
}

.footer__bottom {
  padding-top: var(--s-8);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4) var(--s-8);
  justify-content: space-between;
  align-items: baseline;
  font-size: var(--t-xs);
  /* 0.5 was tuned against the ocean --abyss and cleared 4.5:1 there by luck of
     that one surface being nearly black. A brand with a lighter --abyss drops
     the same alpha to 3.97:1 and the copyright line stops being readable. 0.62
     clears the floor on both. tests/contrast.test.mjs now measures it. */
  color: rgba(255, 255, 255, 0.62);
}
.footer__network {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-4);
  list-style: none;
  padding: 0;
  margin: 0;
}
.footer__network li {
  margin: 0;
}
@media (max-width: 62rem) {
  .footer__top {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .footer__brand {
    grid-column: 1 / -1;
  }
}

/* --------------------------------------------------------------------------
   16. Breadcrumbs & article furniture
   -------------------------------------------------------------------------- */

.crumbs {
  font-size: var(--t-xs);
  font-family: var(--f-tech);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--s-6);
}
.crumbs ol {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  padding: 0;
  margin: 0;
}
.crumbs li {
  margin: 0;
}
.crumbs li + li::before {
  content: "/";
  margin-right: var(--s-2);
  color: var(--slate-300);
}
.crumbs a {
  color: inherit;
  text-decoration: none;
}
.crumbs a:hover {
  color: var(--accent-ink);
}
.hero .crumbs,
.on-dark .crumbs {
  color: var(--sky);
}
.hero .crumbs li + li::before {
  color: rgba(151, 203, 220, 0.5);
}

.byline {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-4);
  align-items: center;
  font-size: var(--t-sm);
  color: var(--text-muted);
  margin-bottom: var(--s-8);
  padding-bottom: var(--s-6);
  border-bottom: 1px solid var(--line);
}

.refs {
  margin-top: var(--s-12);
  padding-top: var(--s-6);
  border-top: 1px solid var(--line-2);
  font-size: var(--t-sm);
  color: var(--text-muted);
}
.refs h2,
.refs h3 {
  font-family: var(--f-tech);
  font-size: var(--t-xs);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted);
}
.refs ol {
  padding-left: 1.25em;
}
.refs li {
  margin-bottom: var(--s-3);
  line-height: var(--lead-normal);
}
.refs a {
  word-break: break-word;
}

/* A media piece's source line carries bare addresses, and an address is one long
   unbreakable word. Added 2026-09-22, after a piece whose sources included a
   150-character URL pushed its page sideways at 320px. */
.footnote {
  overflow-wrap: anywhere;
}

/* Briefings cite their sources inline, as bare addresses in running prose, and the same
   unbreakable-word problem pushed four media pages sideways at 320px on 2026-09-22. */
.prose p,
.prose li {
  overflow-wrap: anywhere;
}

/* --------------------------------------------------------------------------
   17. Network strip — quiet cross-links between the sites
   -------------------------------------------------------------------------- */

.netstrip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--s-4);
}
.netstrip a {
  display: block;
  padding: var(--s-4) var(--s-6);
  border: 1px solid var(--line);
  border-radius: var(--r-btn);
  text-decoration: none;
  background: var(--white);
  transition: border-color var(--d-2) var(--e-out), transform var(--d-2) var(--e-out);
}
.netstrip a:hover {
  border-color: var(--line-3);
  transform: translateY(-2px);
}
.netstrip strong {
  display: block;
  font-size: var(--t-base);
  margin-bottom: 0.15rem;
}
.netstrip span {
  display: block;
  font-size: var(--t-sm);
  color: var(--text-muted);
  line-height: var(--lead-normal);
}
.on-dark .netstrip a,
.section--abyss .netstrip a,
.section--deep .netstrip a {
  background: color-mix(in srgb, var(--ocean) 24%, transparent);
  border-color: var(--line-dark);
}
.on-dark .netstrip strong,
.section--abyss .netstrip strong {
  color: var(--white);
}
.on-dark .netstrip span,
.section--abyss .netstrip span {
  color: var(--sky);
}

/* A netstrip that carries the system marks. It used to carry the full wordmarks
   and set no name as text at all, which meant every system name reached a
   crawler as alt text and nothing else. So the mark shrank to its icon and the
   name became a real heading beside it: the same picture to a reader, an actual
   name to everything that is not one.

   The card is dark because the icons are not. Seven marks drawn in seven brand
   hues — SoilScrubber's gold worst of all, 1.32:1 on foam — were being asked to
   carry themselves on a white surface, and the gold one had already needed a
   recolored wordmark to stay legible there. On #0B1226 every one of them keeps
   its own color and gains contrast instead of losing it. That value is the
   founder's, and it is the one surface in this stylesheet that is not a token:
   darker than --deep, cooler than --abyss, picked for the marks rather than for
   the palette. It is scoped to this component and nothing else may use it.

   Icons run from 1:1 to nearly 3:1, so they are sized by height and left to find
   their own width, with a ceiling so the widest cannot crowd the name. The
   ceiling needs `object-fit` beside it and the first draft of this rule did not
   have one: a definite height plus a clamped width is a broken aspect ratio, not
   a smaller mark, and HABslayer at 2.93:1 painted 32% narrow on the flagship
   page while the other six measured true. `contain` letterboxes the one mark
   wider than the box instead of squashing it. */
.netstrip--marks a {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas:
    "mark name"
    "body body";
  align-items: center;
  column-gap: var(--s-4);
  row-gap: var(--s-3);
  background: #0b1226;
  border-color: rgba(151, 203, 220, 0.2);
}
.netstrip--marks a:hover {
  border-color: var(--sky);
}
.netstrip--marks .netstrip__mark {
  grid-area: mark;
  display: block;
  height: 2.5rem;
  width: auto;
  max-width: 5.5rem;
  object-fit: contain;
}
.netstrip--marks h3,
.netstrip--marks strong {
  grid-area: name;
  margin: 0;
  margin-bottom: 0;
  font-size: var(--t-base);
  line-height: var(--lead-tight);
  color: var(--white);
}
.netstrip--marks span {
  grid-area: body;
  color: var(--sky);
}
@media (max-width: 48rem) {
  .netstrip--marks .netstrip__mark {
    height: 2.125rem;
  }
}

/* --------------------------------------------------------------------------
   18. Motion
   -------------------------------------------------------------------------- */

[data-reveal] {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity var(--d-4) var(--e-out), transform var(--d-4) var(--e-out);
  will-change: opacity, transform;
}
[data-reveal].is-in {
  opacity: 1;
  transform: none;
}
[data-reveal-delay="1"] {
  transition-delay: 90ms;
}
[data-reveal-delay="2"] {
  transition-delay: 180ms;
}
[data-reveal-delay="3"] {
  transition-delay: 270ms;
}

@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;
  }
  [data-reveal] {
    opacity: 1;
    transform: none;
  }
}

/* --------------------------------------------------------------------------
   18b. Forms — the situation report, and nothing else
   -------------------------------------------------------------------------- */

.form {
  display: grid;
  gap: var(--s-6);
  max-width: 42rem;
}
.form__row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-6);
}
@media (max-width: 40rem) {
  .form__row {
    grid-template-columns: minmax(0, 1fr);
  }
}

.field {
  display: grid;
  gap: var(--s-2);
}
.field > label {
  font-family: var(--f-tech);
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-strong);
}
.field__hint {
  font-size: var(--t-sm);
  line-height: var(--lead-normal);
  color: var(--text-muted);
}
.field__optional {
  text-transform: none;
  letter-spacing: 0;
  font-weight: 400;
  color: var(--text-muted);
}

.field input,
.field textarea,
.field select {
  font: inherit;
  color: var(--text-body);
  background: var(--white);
  border: 1px solid var(--line-3);
  border-radius: var(--r-btn);
  padding: 0.75rem 0.875rem;
  width: 100%;
  transition: border-color var(--d-1) var(--e-out), box-shadow var(--d-1) var(--e-out);
}
.field textarea {
  min-height: 7rem;
  resize: vertical;
  line-height: var(--lead-normal);
}
.field input:hover,
.field textarea:hover {
  border-color: var(--slate-500);
}
.field input:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--accent-cta);
  box-shadow: 0 0 0 3px var(--focus);
}
.field input[aria-invalid="true"],
.field textarea[aria-invalid="true"] {
  border-color: var(--ember);
}

/* The honeypot. Off-screen rather than display:none, which some bots detect. */
.field--trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form__status {
  border-radius: var(--r-card);
  padding: var(--s-4) var(--s-6);
  font-size: var(--t-sm);
  line-height: var(--lead-normal);
  border: 1px solid transparent;
}
.form__status:empty {
  display: none;
}
.form__status[data-state="working"] {
  background: var(--slate-100);
  color: var(--slate-700);
  border-color: var(--line-2);
}
.form__status[data-state="error"] {
  background: rgba(214, 82, 0, 0.08);
  color: #8f3800;
  border-color: rgba(214, 82, 0, 0.3);
}
.form__status[data-state="sent"] {
  background: rgba(0, 151, 152, 0.1);
  color: #00595a;
  border-color: rgba(0, 151, 152, 0.32);
}

.form button[type="submit"][aria-disabled="true"] {
  opacity: 0.6;
  cursor: progress;
}

/* --------------------------------------------------------------------------
   19. Utilities
   -------------------------------------------------------------------------- */

.stack > * + * {
  margin-top: var(--s-4);
}
.stack-6 > * + * {
  margin-top: var(--s-6);
}
.stack-8 > * + * {
  margin-top: var(--s-8);
}
.stack-12 > * + * {
  margin-top: var(--s-12);
}

.mt-0 {
  margin-top: 0;
}
.mb-0 {
  margin-bottom: 0;
}
.mt-6 {
  margin-top: var(--s-6);
}
.mt-8 {
  margin-top: var(--s-8);
}
.mt-12 {
  margin-top: var(--s-12);
}

.text-center {
  text-align: center;
}
.center {
  margin-inline: auto;
}
.measure {
  max-width: 62ch;
}
.measure-tight {
  max-width: 46ch;
}

.rule {
  border: 0;
  border-top: 1px solid var(--line);
  margin: 0;
}
.on-dark .rule,
.section--abyss .rule,
.section--deep .rule {
  border-top-color: var(--line-dark);
}

/* <x-video> — the poster state, and the player that replaces it.

   The button carries the poster rather than sitting on top of it, so the whole
   frame is one hit target and there is nothing to mis-click into. Sizing is by
   aspect-ratio on the figure rather than on the iframe, because the iframe does
   not exist until the click and a box that resizes at that moment would jump the
   page under a reader who just pressed play. */
.x-video {
  margin: 0 0 var(--s-8);
}
.x-video__frame {
  position: relative;
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: var(--abyss);
  border-radius: var(--r-card);
  overflow: hidden;
  cursor: pointer;
  box-shadow: var(--sh-lg);
  aspect-ratio: 16 / 9;
}
.x-video__frame > .x-media,
.x-video__frame > .x-media > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.x-video__cue {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  padding: 0.75rem 1.25rem 0.75rem 1.1rem;
  border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--abyss) 72%, transparent);
  color: var(--white);
  font-family: var(--f-tech);
  font-size: var(--t-sm);
  font-weight: 600;
  letter-spacing: var(--track-eyebrow);
  backdrop-filter: blur(6px);
  transition: background var(--d-2) var(--e-out), transform var(--d-2) var(--e-out);
}
.x-video__frame:hover .x-video__cue,
.x-video__frame:focus-visible .x-video__cue {
  background: var(--accent-cta);
  color: var(--accent-on);
  transform: translate(-50%, -50%) scale(1.04);
}
/* The player REPLACES the button rather than sitting inside it, so it cannot
   inherit the button's box. It has to carry the ratio itself: `height: 100%`
   resolved against the auto-height <figure> gave the iframe no height at all,
   and it collapsed to the 150px an iframe defaults to — a letterboxed sliver
   inside a full-width focus ring, which is what a played video looked like. */
.x-video__player {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  border: 0;
  border-radius: var(--r-card);
  background: var(--abyss);
}
/* A caption under a video belongs to the video, so it runs to the same edge.
   The global 60ch measure is right for a prose caption under a photograph and
   wrong here: it stopped the line well short of the player and broke the text
   at whatever word happened to land there. */
.x-video figcaption {
  margin-top: var(--s-3);
  max-width: 100%;
}
@media (prefers-reduced-motion: reduce) {
  .x-video__frame:hover .x-video__cue,
  .x-video__frame:focus-visible .x-video__cue {
    transform: translate(-50%, -50%);
  }
}

.frame {
  border-radius: var(--r-card);
  overflow: hidden;
  box-shadow: var(--sh-lg);
}
.frame > img,
.frame .x-media,
.frame .x-media > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.ratio-4-3 {
  aspect-ratio: 4 / 3;
}
.ratio-3-2 {
  aspect-ratio: 3 / 2;
}
.ratio-16-9 {
  aspect-ratio: 16 / 9;
}
.ratio-1-1 {
  aspect-ratio: 1 / 1;
}
.ratio-portrait {
  aspect-ratio: 4 / 5;
}

@media (max-width: 40rem) {
  .hide-sm {
    display: none;
  }
}
@media (min-width: 40.0625rem) {
  .only-sm {
    display: none;
  }
}

/* --------------------------------------------------------------------------
   20. Print
   -------------------------------------------------------------------------- */

@media print {
  .nav,
  .cta,
  .footer__network,
  .btn {
    display: none !important;
  }
  body {
    background: #fff;
    color: #000;
    font-size: 11pt;
  }
  .hero {
    min-height: auto;
    padding: 1rem 0;
    color: #000;
    background: #fff;
  }
  .hero__media,
  .hero__scrim {
    display: none;
  }
  .hero h1,
  .hero .lede {
    color: #000;
  }
  a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: 9pt;
    color: #555;
  }
}

/* ── the media feed ─────────────────────────────────────────────────────────
 *
 * A filterable archive. AUTHORING.md says not to write new CSS and to say so instead when
 * the system lacks something; this is that case, said out loud rather than smuggled into a
 * page as a one-off block. The system had cards, chips and a grid, and no feed, no filter
 * bar and no toggle state on a chip.
 *
 * THE HEAD IS ONE LINE. The name of the page, the count and the controls share a single
 * row; the chips live in a tray under it that is shut until somebody wants it. It began as
 * a title band plus a controls band plus three rows of chips standing open, which on a
 * laptop was most of the first screen spent before a reader saw one piece. This page's
 * whole job is the list, so the head costs one row and the first cards clear the fold on a
 * phone.
 *
 * THIS PAGE HAS NO HERO, SO IT HAS TO CLEAR THE NAV ITSELF. `.nav` is `position: fixed`
 * and every hero pays for it with `padding-top: calc(var(--nav-h) + 4rem)`. A section at
 * the top of a page with no hero gets no such padding and the nav crops whatever is under
 * it - which is what was happening to the `h1` here. `.media-index` pays the same rent.
 *
 * The chips are real checkboxes with the box hidden, not divs listening for clicks. A
 * keyboard tabs through them, space toggles them, a screen reader announces the state, and
 * the whole filter works as a GET form before any script loads.
 */

/* The nav is fixed, so the first band on a page without a hero has to clear it. The extra
   step over --nav-h is deliberately smaller than a hero's 4rem: this is an index, and the
   air a hero needs above a headline is air this page spends on cards. */
.media-index {
  padding-top: calc(var(--nav-h) + clamp(1.5rem, 0.9rem + 2.2vw, 3rem));
}

/* One row: the title on the left, the controls hard right on the same baseline. They wrap
   to two rows only when the viewport genuinely cannot hold them, and the wrap is a real
   break rather than a ragged half-line because the bar carries `margin-left: auto`. */
.filters {
  margin: 0 0 var(--s-8);
  border-bottom: 1px solid var(--line);
}
.filters__head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--s-3) var(--s-6);
  padding-bottom: var(--s-4);
}
/* The stylesheet's ordinary h1 - a media index does not need a headline announcing that it
   is a media index, but the one word naming the page should be set at the size every other
   page sets its name at. Only the margin goes, because the row owns the spacing now. */
.filters__title {
  margin: 0;
}

.filters__bar {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex-wrap: wrap;
  margin-left: auto;
}
.filters__status {
  margin: 0;
  font-family: var(--f-tech);
  font-size: var(--t-xs);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The disclosure button. `hidden` in the HTML and revealed by the script, so a reader with
   no JavaScript never meets a button that cannot open anything. */
.filters__toggle[aria-expanded="true"] {
  background: rgba(0, 19, 36, 0.04);
  border-color: var(--text-strong);
}
.filters__icon {
  flex: none;
}
.filters__badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.25rem;
  height: 1.25rem;
  padding: 0 0.3rem;
  border-radius: var(--r-pill);
  background: var(--accent-cta);
  color: var(--accent-on);
  font-family: var(--f-tech);
  font-size: var(--t-xs);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

/* The tray opens and shuts on `display`, and only the content fades. The obvious thing to
   write here is the `grid-template-rows: 0fr → 1fr` trick, which animates a panel of
   unknown height without a magic max-height - and it was written, and it is out again. A
   transition on a track size is the one thing on this page whose correctness depends on the
   animation finishing: a tab that stops painting mid-transition leaves the tray stuck at
   its open height with the attribute saying `closed`, which is exactly what it did under
   test. Safari only learned to animate the property at all in 17.4. `display: none` cannot
   be stuck half-open, and it takes the chips out of the tab order for free, which the
   zero-height version needed a delayed `visibility` to do. What is left animated is
   opacity and a few pixels of travel, neither of which can strand the layout. */
.filters__tray {
  display: grid;
  grid-template-rows: 1fr;
}
.filters__panel {
  overflow: hidden;
}
.filters[data-tray="closed"] .filters__tray {
  display: none;
}
.filters[data-tray="open"] .filters__groups {
  animation: filters-tray-in var(--d-2) var(--e-out) both;
}
@keyframes filters-tray-in {
  from {
    opacity: 0;
    transform: translateY(-0.25rem);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.filters__groups {
  display: grid;
  gap: var(--s-3);
  padding: var(--s-2) 0 var(--s-6);
}
/* Legend column fixed so every kind's chips start at the same x. */
.filters__group {
  display: grid;
  grid-template-columns: 5.5rem minmax(0, 1fr);
  align-items: baseline;
  gap: var(--s-2) var(--s-4);
}
.filters__chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}
.filters__legend {
  font-family: var(--f-tech);
  font-size: var(--t-xs);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* The input is hidden from sight and not from anything else: no display:none, because a
   hidden input cannot be focused and the chips would stop being reachable by keyboard. */
.chip--toggle {
  cursor: pointer;
  user-select: none;
  background: transparent;
  border: 1px solid var(--line);
  transition: background var(--d-2) var(--e-out), border-color var(--d-2) var(--e-out),
    color var(--d-2) var(--e-out);
}
.chip--toggle input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}
.chip--toggle:hover {
  border-color: var(--accent);
  color: var(--accent-ink);
}
.chip--toggle:has(input:checked) {
  background: var(--accent-cta);
  border-color: var(--accent-cta);
  color: var(--accent-on);
}
.chip--toggle:has(input:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.chip__count {
  font-variant-numeric: tabular-nums;
  opacity: 0.55;
}

.feed {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--s-6);
  grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
}
.feed__item > .card {
  height: 100%;
}
.feed__empty {
  margin: var(--s-12) 0;
  text-align: center;
}
.feed__sentinel {
  display: flex;
  justify-content: center;
  margin: var(--s-12) 0 0;
}

/* Below this the legend cannot keep a column of its own without squeezing the chips into a
   two-word-per-line ribbon, so it becomes a line above them. The head still holds one row:
   at 360px "Media" is 36px and the count and the button together are under 200px. */
/* A thumb, not a cursor. The network's own rule is that a button in `main` is at least
   40px tall on a phone, and `btn--sm` is 35; the filter toggle is the one control on this
   page that a reader has to hit before anything else happens, so it gets the full 44. The
   chips are labels rather than buttons and clear the WCAG minimum at their desk size, but
   they are the actual work of this page on a phone and 32px is worth the two extra rows. */
@media (max-width: 48rem) {
  .filters__bar .btn {
    min-height: 44px;
  }
  .chip--toggle {
    min-height: 32px;
  }
}

/* Below this the word comes off the button and the icon carries it. The row has to hold
   the page title, the count and the control on one line at 320px in two different heading
   faces, and Sophia Delta's is the wider of them: with the word on, that row wrapped. The
   button keeps `aria-label="Filters"`, so nothing is lost to a screen reader and the name
   does not change between widths; the badge stays, because a shut tray that is filtering
   has to say so. */
@media (max-width: 40rem) {
  .filters__toggle-text {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .filters__group {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s-1);
  }
  .filters__head {
    gap: var(--s-2) var(--s-3);
  }
  .filters__bar {
    gap: var(--s-2);
  }
}

/* One column of cards on a phone rather than a 17rem minimum that leaves a ragged gutter
   at 320-360px, and a tighter gap between them. */
@media (max-width: 30rem) {
  .feed {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s-4);
  }
}

@media (prefers-reduced-motion: reduce) {
  .chip--toggle {
    transition: none;
  }
  .filters[data-tray="open"] .filters__groups {
    animation: none;
  }
}
