/**
 * MedChem Manta — landing page.
 *
 * The page is a descent. A thin band of daylight sits at the very top of the
 * hero and the water is deep blue within about 300 px; from there every
 * section paints its own slice, each ending on the colour the next one begins
 * with, down to near-black at the floor. Nothing about the water needs
 * JavaScript.
 *
 * Colour, type and radius come from the app's own system — see CLAUDE.md, and
 * src/data/taxonomies.ts for the accents, which are the same hexes the library
 * cards use so the download feels like the same object.
 */

@import './fonts.css';

/* ------------------------------------------------------------------ */
/* Tokens                                                               */
/* ------------------------------------------------------------------ */

:root {
  --w-abyss: #030d16;

  --ink: #e9f2f9;
  --ink-dim: #c2dbec;

  /* Taxonomy accents — src/data/taxonomies.ts, unchanged. */
  --ochre: #b5834a;
  --wine: #9e5b6e;
  --sage: #5b8a72;
  --teal: #4a8f9e;
  --plum: #8a6fae;
  --slate: #3d5a80;
  --sky: #98c1d9;

  /* The accents lightened until they carry words. Each hex below is measured
     against the top of the band it appears on — where a section's label
     sits — and clears 4.5:1 there; the raw accents run 2.3:1 to 3.1:1 and
     stay on furniture. #privacy and #questions need no tint: their water is
     dark enough that --sky itself is 7:1 and 8.6:1.

     INVARIANT: a tint belongs to one band. Move a section up the page and it
     must be re-measured, not carried over. */
  --sky-text: #d5e6f0; /* on #156c8a — 4.63:1 */
  --ochre-text: #eac294; /* on #1a5975 — 4.63:1 */
  --wine-text: #e5bfca; /* on #1a5975 — 4.63:1 */
  --sage-text: #a6d2bb; /* on #1a5975 — 4.60:1 */

  --teal-text: #8abdc8; /* on #154a66 — 4.63:1 */
  --plum-text: #b8a3dd; /* on #113f5a — 4.95:1 */

  /* The shelf lockups, one step down from the -text tints: deep enough to
     read as the category's own colour, which the pale versions did not. The
     counts are large text and clear the 3:1 that applies there; the 15px
     labels are normal text and sit under 4.5:1. Known and accepted — the
     category name is repeated in the description beneath each one. */
  --ochre-lit: #cf995b; /* on #1a5975 — 3.06:1 */
  --wine-lit: #c396b3; /* on #1a5975 — 3.05:1, hue pulled off red */
  --sage-lit: #76b093; /* on #1a5975 — 3.08:1 */

  --display: 'Space Grotesk', 'IBM Plex Sans', ui-sans-serif, system-ui, sans-serif;
  --body: 'IBM Plex Sans', ui-sans-serif, system-ui, sans-serif;
  --mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  --gut: clamp(20px, 7vw, 120px);

  /* Type scale. Five steps, and every piece of running text on the page takes
     one of them — before this the same role appeared at 14px, 14.5px, 15px and
     16px in different sections, which is four decisions where there should be
     one.

     Two things sit deliberately outside it and are not tokens: the section
     headings (`.stratum h2`) and the hero's wordmark and tagline, which are
     display sizes tuned against the artwork rather than steps on a ladder.
     The `.phone` mockups are also outside it — their type is a scale model of
     a phone screen, so scaling it would break the illusion. */
  --t-sub: 19px; /* sub-heads inside a section */
  --t-lede: clamp(17.5px, 1.7vw, 20px); /* section intros, the hero sentence */
  --t-body: 16.5px; /* card and list copy */
  --t-label: 13px; /* mono, tracked: depth markers, pills */
  --t-micro: 12.5px; /* mono, smallest: qualifiers, step numbers */
  /* Overwritten from the nav's measured height by dive.js; this is the
     no-script fallback, not a second source of truth. */
  --nav-h: 56px;
  --measure: 62ch;
}

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

html {
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

body {
  margin: 0;
  background: var(--w-abyss);
  color: var(--ink);
  font-family: var(--body);
  font-size: 17px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

h1,
h2,
h3 {
  font-family: var(--display);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.02em;
  text-wrap: balance;
  margin: 0;
}

p {
  margin: 0;
}

a {
  color: inherit;
}

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

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: #fff;
  color: #0f2b42;
  padding: 10px 16px;
  border-radius: 0 0 8px 0;
  font-weight: 600;
}

.skip:focus {
  left: 0;
}

/* Depth readings opt out of the small-caps treatment around them: in
   chemistry "M" is molar, and this page is read by people for whom that is
   not a quibble. */
.depth {
  text-transform: none;
  font-style: normal;
}

.eyebrow {
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------------ */
/* Navigation                                                           */
/* ------------------------------------------------------------------ */

/* The bar is dark from the first pixel. The daylight band is only about
   150 px tall now, so a nav that changed colour on scroll would flip almost
   immediately — a flicker, not a moment. */
.nav {
  position: sticky;
  top: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 12px var(--gut);
  background: rgba(6, 18, 30, 0.55);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid rgba(152, 193, 217, 0.16);
}

/* The footer's lockup at nav size: the same mark file beside the name, in the
   bold IBM Plex Sans the hero wordmark and the icons use. */
.nav__mark {
  display: flex;
  align-items: center;
  gap: 9px;
  font-family: var(--body);
  font-weight: 700;
  font-size: 18px;
  letter-spacing: -0.02em;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
}

.nav__manta {
  flex: 0 0 auto;
  width: 32px;
  height: auto;
}

.nav__links {
  display: flex;
  align-items: center;
  gap: clamp(12px, 2vw, 26px);
  font-size: 15px;
  color: var(--ink-dim);
}

.nav__links a {
  text-decoration: none;
}

.nav__links a:hover {
  color: #fff;
}

@media (max-width: 700px) {
  .nav__links a {
    display: none;
  }
}

/* ------------------------------------------------------------------ */
/* The water column                                                     */
/* ------------------------------------------------------------------ */

.dive {
  position: relative;
  background: var(--w-abyss);
}

/* Each stratum paints its own slice, ending on the colour the next one starts
   from. Keying the water to the sections rather than to document height keeps
   it in register with the copy: a longer section deepens more slowly instead
   of dragging a band out from under its own text. */
/* Sunlit: warm at the very surface, turquoise under it, then the same blue
   the rest of the page was always going to be.
 *
 * Two layers. The linear one carries the hue journey — a real descent travels
 * through hue, not only through darkness, because water absorbs red first and
 * green next. The radial one is the sun itself, offset to the right so it
 * sources the light shafts rather than sitting flat across the whole width; a
 * warm band edge to edge reads as a stripe, not as a sky.
 *
 * INVARIANT: the nav bar covers roughly the first 62 px of this section, so a
 * stop above that is invisible. The gold runs to 96 px for that reason — the
 * first attempt put it at 0–44 px and none of it could be seen. Every stop
 * from 48% down is unchanged, so the rest of the column still lands on
 * the colour #why picks it up with. */
#top {
  background:
    radial-gradient(
      44% 12% at 68% -5%,
      rgba(255, 243, 213, 0.4),
      rgba(255, 236, 198, 0.14) 46%,
      rgba(255, 236, 198, 0) 80%
    ),
    linear-gradient(
      180deg,
      #fff4dc 0%,
      #fff2d8 0.25%,
      #fff0d4 0.5%,
      #ffeed2 0.75%,
      #fbedd4 1.05%,
      #f0ecd8 1.35%,
      #e4ebdc 1.65%,
      #dceade 1.9%,
      #d5e8dd 2.5%,
      #cce6db 3.2%,
      #c0e4da 4%,
      #b0e1d7 4.9%,
      #9bddd5 5.9%,
      #85d9d1 6.9%,
      #71d4ce 7.9%,
      #5ececa 8.9%,
      #4fc7c6 9.9%,
      #43bec1 10.9%,
      #3bb3bb 11.9%,
      #35a7b5 12.9%,
      #309caf 13.9%,
      #2991aa 14.9%,
      #2387a7 15.9%,
      #2185a6 16.2%,
      #1f82a5 16.5%,
      #1e80a3 16.9%,
      #1c7da2 17.4%,
      #1b7ba0 18%,
      #1a799e 18.8%,
      #19779c 19.8%,
      #18759a 21.3%,
      #177497 23.1%,
      #167294 25.4%,
      #167192 28.2%,
      #167090 31.6%,
      #166f8e 35.6%,
      #166e8c 41%,
      #156d8b 47.3%,
      #156d8a 54.7%,
      #156d8a 63.3%,
      #156c8a 71.8%,
      #156c8a 81.2%,
      #156c8a 90.2%,
      #156c8a 100%
    );
}

#why {
  background: linear-gradient(180deg, #156c8a, #1a5975);
  padding-top: clamp(44px, 6vh, 80px);
  /* Seventeen pixels shallower than the other bands — about five metres at
     the page's 0.29 m/px (see .stratum) — because this one has less lead-in
     above its heading than the rest and lands deeper into itself otherwise. */
  scroll-margin-top: calc(var(--nav-h) - 33px);
}

#library {
  background: linear-gradient(180deg, #1a5975, #154a66);
}

#bioisosteres {
  background: linear-gradient(180deg, #154a66, #113f5a);
}

#study {
  background: linear-gradient(180deg, #113f5a, #0d314a);
}

#privacy {
  background: linear-gradient(180deg, #0d314a, #092135);
}

#questions {
  background: linear-gradient(180deg, #092135, var(--w-abyss));
}

/* Marine snow is a fixed canvas: it follows the viewport, not the document,
   and sits at z-index 3 so it is in front of the section backgrounds. Behind
   them it renders as nothing at all — every band paints over it. */
.snow {
  position: fixed;
  inset: 0;
  z-index: 3;
  width: 100%;
  height: 100%;
  opacity: 0.55;
  pointer-events: none;
}

/* ------------------------------------------------------------------ */
/* Depth gauge — right-hand edge                                        */
/* ------------------------------------------------------------------ */

/* On the right, mirrored, so it reads as an instrument bolted to the edge of
   the page rather than a margin the copy has to be indented around. */
.gauge {
  position: fixed;
  right: 26px;
  top: 84px;
  bottom: 40px;
  /* The labels are nowrap, so the longest tick sets this. The rail states a
     section's name in its short form — "Daily dive", "Watertight" — where the
     eyebrow gives it in full. */
  width: 178px;
  z-index: 30;
  pointer-events: none;
  color: var(--ink);
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}

/* Only the 0 m tick inverts, and only while the hero's pale water is behind
   it: that one mark sits on water running from #fff4dc to turquoise, where a
   white label measures 1.01:1. Every tick below it is over blue by the time
   it is on screen, so inverting the whole rail made five legible marks look
   wrong to fix one. dive.js sets the class; the readout carries its own dark
   chip and never needed either treatment. */
.gauge--bright .gauge__tick[data-tick='0'] {
  color: #0a2c40;
}

.gauge--bright .gauge__tick[data-tick='0'] span {
  text-shadow:
    0 1px 2px rgba(255, 255, 255, 0.7),
    0 0 6px rgba(255, 255, 255, 0.8);
}

.gauge__line {
  position: absolute;
  right: 0;
  top: 0;
  bottom: 0;
  width: 1px;
  background: rgba(233, 242, 249, 0.26);
}

.gauge__tick {
  position: absolute;
  right: 0;
  display: flex;
  flex-direction: row-reverse;
  align-items: center;
  gap: 8px;
  transform: translateY(-50%);
  transition:
    opacity 300ms ease,
    color 300ms ease;
  /* Readable on first sight rather than only once passed: at 0.4 the marks
     ahead of the reader were a hint, not a label. */
  opacity: 0.72;
}

.gauge__tick::before {
  content: '';
  width: 10px;
  height: 1px;
  background: currentColor;
  flex: 0 0 auto;
  transition: width 300ms ease;
}

.gauge__tick span {
  font-size: 11px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  white-space: nowrap;
  /* No chip: the rail should read as marks on the water, not as a row of
     labels. A tight shadow plus a wider dark glow does the same work without
     drawing a box — the glow is what carries them over the pale surface at the
     top, where a 1 px shadow alone would not. */
  text-shadow:
    0 1px 2px rgba(2, 14, 26, 0.85),
    0 0 4px rgba(2, 14, 26, 0.8),
    0 0 11px rgba(2, 14, 26, 0.65);
  transition: text-shadow 300ms ease;
}

.gauge__tick--passed {
  opacity: 1;
}

.gauge__tick--passed::before {
  width: 18px;
}

/* The live readout rides the rail. */
.gauge__now {
  position: absolute;
  right: -4px;
  display: flex;
  align-items: baseline;
  gap: 3px;
  transform: translateY(-50%);
  padding: 2px 10px 2px 9px;
  border-right: 2px solid var(--sky);
  background: rgba(3, 16, 30, 0.62);
  backdrop-filter: blur(4px);
  border-radius: 4px 0 0 4px;
  font-size: 16px;
  font-weight: 500;
}

.gauge__now small {
  font-size: 11px;
  opacity: 0.7;
}

@media (max-width: 1180px) {
  .gauge {
    right: 14px;
    width: 92px;
  }
  .gauge__tick span {
    display: none;
  }
}

@media (max-width: 820px) {
  .gauge {
    top: auto;
    bottom: 14px;
    right: 14px;
    width: auto;
    height: auto;
  }
  .gauge__line,
  .gauge__tick {
    display: none;
  }
  .gauge__now {
    position: static;
    transform: none;
    background: rgba(7, 20, 30, 0.55);
    backdrop-filter: blur(6px);
    border-radius: 999px;
    border-right: 0;
    padding: 4px 12px;
  }
}

/* ------------------------------------------------------------------ */
/* Section shell                                                        */
/* ------------------------------------------------------------------ */

.stratum {
  position: relative;
  z-index: 2;
  padding: clamp(56px, 8vh, 96px) var(--gut);
  /* Where an anchor jump stops, and with it what the depth readout says as it
     lands. The rail samples --nav-h + 6px below the top of the window, so the
     jump lands that far past the section's own mark, less this margin.

     The page runs at about 0.29 m per pixel — measured, not guessed: with no
     margin at all the 250 m band landed reading 270, which is 20 m over the
     68px it was sampling into. So 50px under --nav-h leaves roughly 56px, or
     about 15 m past the band's own number, which is where a section reads as
     arrived at rather than merely touched. Ten pixels is three metres if this
     wants tuning. The last band cannot travel the full distance; the browser
     clamps it at the foot of the page. */
  scroll-margin-top: calc(var(--nav-h) - 50px);
}

.stratum__inner {
  max-width: 1440px;
  margin: 0 auto;
}

/* Reserve room for the rail wherever it is drawn as a rail. The content column
   is wide enough now that it would reach the gauge at any desktop width, so
   this is no longer capped at 1520 px. */
@media (min-width: 1181px) {
  .stratum,
  .foot {
    padding-right: 236px;
  }
}

@media (min-width: 821px) and (max-width: 1180px) {
  .stratum,
  .foot {
    padding-right: 118px;
  }
}

/* INVARIANT: the accents paint furniture, not words. Every taxonomy hex sits
   between 1.5:1 and 3.1:1 on the band it appears over — fine for a dot, a
   bar or a border, and not enough for text. So the label runs in --ink-dim
   (7.8:1) and the accent moves to the dot beside it. A section that wants a
   coloured label sets --accent-text to a lightened, measured tint; see
   --plum-text, which is 4.95:1 and is what #study uses. */
.stratum__label {
  color: var(--accent-text, var(--ink-dim));
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 18px;
}

.stratum__label::before {
  content: '';
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--accent, var(--sky));
  box-shadow: 0 0 14px 1px var(--accent, var(--sky));
  flex: 0 0 auto;
}

.stratum h2 {
  font-size: clamp(27px, 4.2vw, 42px);
  max-width: 22ch;
}

.stratum__lede {
  margin-top: 18px;
  max-width: var(--measure);
  color: var(--ink-dim);
  font-size: var(--t-lede);
}

/* ------------------------------------------------------------------ */
/* Surface — the hero                                                   */
/* ------------------------------------------------------------------ */

.surface {
  position: relative;
  overflow: hidden;
  /* The hero is the one band with nothing above it to arrive into, so it does
     not take the shared landing offset: the wordmark and the nav's call to
     action should reach the very top of the page. Its own offset is the
     sticky nav's height, and a little more than that clamps to zero however
     tall the bar gets. */
  scroll-margin-top: calc(var(--nav-h) + 20px);
  /* The first screen is the hero and nothing else: the next band's marker
     used to peek over the fold, which gave away the descent before the
     visitor had started it. Centred rather than top-aligned so the extra
     height goes evenly above and below instead of stranding the copy. */
  min-height: calc(100vh - var(--nav-h));
  min-height: calc(100svh - var(--nav-h));
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-top: clamp(40px, 6vh, 76px);
  padding-bottom: clamp(28px, 3.4vh, 44px);
}

/* Sunlight through the surface.
 *
 * Six shafts, each with its own width, angle, period and phase set inline in
 * the markup, so no two are ever in step. They sway, stretch and brighten
 * rather than only sliding sideways — a shaft of light through moving water
 * changes width as much as it changes position.
 *
 * INVARIANT: every light effect in the hero must reach zero before the
 * section's bottom edge. `.surface` clips its children, so an effect still
 * running at that edge is cut off mid-value and shows up as a hard seam
 * across an otherwise continuous water column. The mask below is what
 * guarantees the fade; do not remove it in favour of `overflow: hidden`. */
.rays {
  position: absolute;
  z-index: 2;
  inset: -18% -10% auto -10%;
  height: 125%;
  pointer-events: none;
  /* Fades in as well as out.
   *
   * In: a white shaft over near-white surface water adds nothing but a
   * contrast edge, and those edges are what read as dark lines between the
   * colours at the top of the column. The shafts start once the water is dark
   * enough to show them — about 14% down the section.
   *
   * Out: not until 94%. The light is meant to carry through most of the
   * landing view, which is why this box is 125% of the section rather than
   * 108% — the mask's own percentages are of the box, and at the old height
   * there was no room left to fade in. Section fraction = (mask% x 1.25) -
   * 0.18; recompute both if either number moves.
   *
   * INVARIANT: still zero before the section's bottom edge. `.surface` clips
   * its children, and a shaft cut off mid-value is a hard seam. */
  -webkit-mask-image: linear-gradient(180deg, transparent 14%, #000 26%, #000 62%, transparent 90%);
  mask-image: linear-gradient(180deg, transparent 14%, #000 26%, #000 62%, transparent 90%);
}

.rays i {
  position: absolute;
  top: -24%;
  left: var(--x);
  width: var(--w);
  height: 150%;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.68), rgba(255, 255, 255, 0));
  /* Tightened along with the alpha: past about 16px the shaft stops having an
     edge and reads as a wash, so brightness alone would only have made the
     water paler rather than making the light legible as shafts. */
  filter: blur(15px);
  transform: rotate(var(--rot));
  transform-origin: top center;
  animation: sway var(--dur) ease-in-out infinite;
  animation-delay: var(--delay);
}

@keyframes sway {
  0%,
  100% {
    transform: rotate(var(--rot)) translateX(0) scaleX(1);
    opacity: calc(var(--op) * 0.4);
  }
  30% {
    transform: rotate(calc(var(--rot) - 4.5deg)) translateX(46px) scaleX(1.48);
    opacity: var(--op);
  }
  62% {
    transform: rotate(calc(var(--rot) + 2.5deg)) translateX(-24px) scaleX(0.72);
    opacity: calc(var(--op) * 0.62);
  }
}

/* Surface shimmer under the shafts.
 *
 * Two banded washes rather than one: different angles, different periods, and
 * drifting in opposite directions, so where they cross they brighten and where
 * they part they fade. That interference is what reads as shimmer — a single
 * layer sliding sideways only ever reads as a texture being moved. Each layer
 * carries its own blur, which is why they are pseudo-elements and not one
 * background on the parent.
 *
 * Masked to the top of the section for the same reason the shafts are: nothing
 * in the hero may still be running at the section's bottom edge.
 */
.caustics {
  position: absolute;
  z-index: 1;
  inset: 0 -40% auto -40%;
  height: 62%;
  pointer-events: none;
  /* Same reasoning as the shafts: the shimmer starts below the pale band, or
     the gaps between its bright bands read as lines. This box is 62% of the
     section tall, so 23% here is 14% of the section. */
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 23%, #000 55%, transparent 100%);
  mask-image: linear-gradient(180deg, transparent 0%, #000 23%, #000 55%, transparent 100%);
}

.caustics::before,
.caustics::after {
  content: '';
  position: absolute;
  inset: 0;
}

.caustics::before {
  background: repeating-linear-gradient(
    104deg,
    rgba(255, 255, 255, 0) 0px,
    rgba(255, 255, 255, 0.16) 58px,
    rgba(255, 255, 255, 0) 148px,
    rgba(255, 255, 255, 0.09) 226px,
    rgba(255, 255, 255, 0) 330px
  );
  filter: blur(15px);
  opacity: 0.8;
  animation: drift 19s linear infinite;
}

.caustics::after {
  background: repeating-linear-gradient(
    93deg,
    rgba(255, 255, 255, 0) 0px,
    rgba(255, 255, 255, 0.11) 34px,
    rgba(255, 255, 255, 0) 96px,
    rgba(255, 255, 255, 0.07) 150px,
    rgba(255, 255, 255, 0) 244px
  );
  filter: blur(9px);
  opacity: 0.6;
  animation: drift-back 26s linear infinite;
}

@keyframes drift {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-330px);
  }
}

@keyframes drift-back {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(244px);
  }
}

/* The scrim under the copy. It fades out vertically as well as horizontally —
   an `inset: 0` overlay would end in a straight dark line exactly where the
   hero meets the next band, which is the seam this page cannot afford. */
.scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(
    102deg,
    rgba(3, 18, 34, 0.5) 0%,
    rgba(3, 18, 34, 0.32) 34%,
    rgba(3, 18, 34, 0.1) 58%,
    rgba(3, 18, 34, 0) 72%
  );
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 50%, transparent 86%);
  mask-image: linear-gradient(180deg, #000 0%, #000 50%, transparent 86%);
}

/* No z-index of its own: creating a stacking context here would trap the
   mascot and the light shafts together with no way to order the two. The
   mascot sits in front of the light; the panel sits in front of both.
   `width: 100%` because `.surface` is a flex column and auto side margins
   switch a flex item out of stretch into fit-content — without it the hero
   collapses to the width of its own longest word. */
.hero {
  position: relative;
  width: 100%;
  max-width: 1440px;
  margin: 0 auto;
}

/* OPTION C — the manta swims off the right edge.
 *
 * The box is deliberately wider than the column and pushed past `.hero`'s
 * right edge in viewport units, so on a wide screen it runs to the edge of
 * the water and `.surface`'s clip takes the rest. It also rises above the
 * panel by `--rise`, tucking just under the nav — the manta needs vertical
 * room to be wide, since `object-fit: contain` sizes the artwork to whichever
 * of the two runs out first.
 *
 * It still cannot hang below the panel: the band's bottom IS the panel's
 * bottom (`.hero` has no other in-flow child), and the image is capped to the
 * band. The <img> carries no positioning of its own, so it stays free for a
 * transform animation later.
 */
.hero__manta {
  /* Sized off the shorter of the two axes. Height alone used to set it, which
     is fine on a wide screen but not on a narrow tall one: the artwork is
     1.2x wider than it is tall, so a window with height to spare and no width
     grew the mascot until its wing disappeared behind the title card. The two
     terms cross at about 1440x900, so a normal desktop is unchanged and only
     the narrow end gives ground. The width term is steeper than a plain
     fraction of the viewport so it bites quickly once the water narrows;
     a little of the wing behind the card is fine, the whole wing is not. */
  --band: clamp(340px, min(70.6vh, 58vw - 202px), 843px);
  --drop: clamp(253px, 37vh, 481px);
  position: absolute;
  z-index: 3;
  /* Pushed past the water's right edge, but by less as the window narrows —
     a flat `vw` offset kept shoving the mascot into the gauge rail on a small
     screen while leaving a gap on the card side. Right clearance is the one
     that reads, so it is the one held: the left wing takes the squeeze. */
  right: calc(-20.8vw + 144px);
  top: auto;
  bottom: calc(-1 * var(--drop));
  height: var(--band);
  display: flex;
  align-items: stretch;
  pointer-events: none;
  will-change: transform;
}

/* The <svg> is empty until `manta.js` mounts, so its box has to come from CSS
   alone — `aspect-ratio` stands in for the intrinsic ratio an inline SVG does
   not supply, which also keeps the box (and so `dive.js`'s exit measurement)
   correct while the mascot is torn down offscreen.
   INVARIANT: the ratio is the viewBox `manta.js` writes, `-4 -5 236 196`.
   `overflow: visible` because the flap carries the wingtips outside it. */
.hero__manta .manta {
  display: block;
  height: 100%;
  width: auto;
  aspect-ratio: 236 / 196;
  overflow: visible;
}

/* The copy's ground, above the light. A translucent slate rather than an
   opaque card: the
   backdrop blur keeps the moving light legible behind it, and the wide, soft
   outer shadow bleeds the panel into the water so its rectangle never reads
   as a box dropped on top of the scene. */
.hero__panel {
  position: relative;
  z-index: 4;
  max-width: min(640px, 50%);
  padding: clamp(24px, 2.8vw, 36px);
  border-radius: 22px;
  /* Indigo, and deliberately independent of the water behind it. A
     translucent panel takes its hue from its backdrop, so the card used to
     read cyan over a petrol column and violet over an indigo one. Raising the
     tint's own weight is half the fix; `saturate(0.5)` on the backdrop is the
     other half — it strips the hue out of what shows through while keeping the
     luminance variation that makes the blur worth having. The two trade
     against each other: dropping the tint's alpha lets more of the backdrop
     through, so `saturate` has to come down with it to keep the hue out.
     The tint is solved rather than picked: compositing is linear, so measuring
     the rendered card gives the effective backdrop, and the tint that lands on
     a wanted colour follows. This one targets #151E38 — the mascot's own body —
     so the card and the animal read as the same dark. */
  background: rgba(7, 15, 45, 0.74);
  backdrop-filter: blur(10px) saturate(0.34);
  border: 1px solid rgba(152, 193, 217, 0.13);
  text-align: center;
  /* INVARIANT: this shadow's reach (spread + blur = 40px) must stay under
     `.surface`'s smallest padding, or `overflow: hidden` clips it into a
     hard line across the section boundary. Tightening the padding means
     tightening this first. */
  box-shadow:
    0 0 36px 4px rgba(4, 19, 34, 0.32),
    0 20px 44px -34px rgba(0, 0, 0, 0.7);
}

/* The wordmark, not a sentence: the product name is the first thing read on a
   store landing page, and until now it appeared only in a 26px nav lockup.
   Set in bold IBM Plex Sans rather than the page's display face, to sit closer
   to the lockup's own letterforms — the tagline underneath keeps Space
   Grotesk, so the two are distinguishable at a glance. */
.hero h1 {
  font-family: var(--body);
  font-weight: 700;
  font-size: clamp(36px, 4.8vw, 56px);
  letter-spacing: -0.025em;
  margin-top: 0;
}

.hero__tagline {
  margin-top: 10px;
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(18px, 2.1vw, 23px);
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--sky);
}

/* The plain "what is this" sentence, in open water under the panel. It stays
   above the fold — a store visitor should not have to scroll to find out what
   the thing is — but out of the panel, so the panel carries brand and call to
   action only. The wrapper aligns it to the panel's left edge; the paragraph
   inside keeps its own measure. */
.hero__foot {
  position: relative;
  z-index: 4;
  width: 100%;
  max-width: 1440px;
  margin: 58px auto 0;
}

.hero__foot .stratum__label {
  margin-bottom: 12px;
}

.hero__note {
  /* Held clear of the mascot: the tail sweeps left as it straightens into the
     swim, and at 54ch the first line ran under the arc it draws. */
  max-width: 41ch;
  font-size: var(--t-lede);
  color: rgba(233, 242, 249, 0.92);
}

/* Stacked layout.
 *
 * `.hero` becomes `display: contents` so the panel and the mascot are flex
 * items of the section itself rather than a single block — that is what lets
 * `space-between` distribute the slack around the mascot instead of dumping
 * it all below the panel. The order down the screen is panel, mascot, opening
 * sentence, cue, with the cue sitting at the bottom of the first screen where
 * it belongs.
 *
 * The whole hero still has to fit one screen down to that cue, so the mascot
 * is the thing that gives ground on short phones: it is the only element here
 * that can shrink without costing information.
 */
@media (max-width: 980px) {
  .surface {
    justify-content: space-between;
    padding-top: clamp(14px, 2.2vh, 30px);
    padding-bottom: clamp(14px, 2vh, 28px);
  }
  .hero {
    display: contents;
  }
  .hero__panel {
    max-width: 100%;
  }
  .hero__manta {
    position: static;
    /* `height` is a fixed band in the desktop layout; stacked, it has to come
       from the artwork or the box leaves 400 px of empty water behind it. */
    height: auto;
    width: 58%;
    margin: 0 0 0 auto;
    transform: none !important;
  }
  .hero__manta .manta {
    width: 100%;
    height: auto;
  }
  .hero__foot {
    margin-top: 0;
  }
  .cue {
    margin-top: 14px;
  }
}

/* How big the mascot can be on a phone is set by how much slack the copy
   leaves, and that turns on width as much as height: a narrower screen wraps
   the badges and the qualifier onto extra rows, so the panel grows and the
   mascot has to give the room back. Measured slack between the three stacked
   blocks, with the mascot at 58%:
     430x932  218px      390x844  131px
     360x780   42px      375x667   19px
   Hence two tiers rather than one — phones with room in both directions get a
   much larger mascot, short ones get a smaller one, and the awkward
   narrow-but-tall middle keeps what it has. */
@media (max-width: 980px) and (min-width: 380px) and (min-height: 800px) {
  .hero__manta {
    width: 74%;
  }
}

@media (max-width: 980px) and (max-height: 700px) {
  .hero__manta {
    width: 40%;
  }
  .hero__panel {
    padding: 20px;
  }
  .hero h1 {
    font-size: 32px;
  }
}

.badges {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 28px;
}

/* The nav's call to action is the download badge at nav size — same shape,
   same ink, same light. Two buttons that mean the same thing should not be
   two designs, and this is the one place on the page where both are on
   screen together. */
.badge,
.nav__cta {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 11px 20px;
  border-radius: 12px;
  background: #eef5fa;
  color: #0d2438;
  text-decoration: none;
  font-weight: 600;
  font-size: 16px;
  line-height: 1.2;
  transition: box-shadow 220ms ease;
}

/* Smaller, because it lives in a 56px bar and the hero's pair is the download
   control the page is actually pointing at. */
.nav__cta {
  padding: 8px 16px;
  font-size: 15px;
  white-space: nowrap;
}

.badge small {
  display: block;
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.06em;
  opacity: 0.68;
}

/* At rest the glow is tight enough to read as a lit surface rather than a
   halo; on hover it lifts a little. That change is the entire hover state —
   the buttons used to also translate, which on a page this still read as
   fidgeting rather than as feedback. */
.badge--glow,
.nav__cta {
  background: var(--sky);
  box-shadow: 0 0 18px -6px rgba(152, 193, 217, 0.62);
}

.badge--glow:hover,
.badge--glow:focus-visible,
.nav__cta:hover,
.nav__cta:focus-visible {
  box-shadow:
    0 0 26px -5px rgba(152, 193, 217, 0.72),
    0 0 10px -3px rgba(214, 236, 248, 0.45);
}

/* The call to action and its qualifier centre inside the slate; the headline
   and lede stay ranged left, so the panel reads as copy with a signature
   under it rather than as a fully centred poster. */
.hero__panel .badges {
  justify-content: center;
}

.hero__panel .hero__meta {
  text-align: center;
}

.hero__meta span {
  white-space: nowrap;
}

.hero__meta {
  margin-top: 20px;
  font-family: var(--mono);
  font-size: var(--t-micro);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-dim);
}

/* Closes the hero rather than opening the next band, so it is on screen when
   the page loads — the one affordance telling a visitor the page goes
   somewhere. The arrow leads the words rather than trailing them. */
.cue {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  /* Sits about midway between the sentence above it and the bottom of the
     first screen. `.surface` centres its column, so half of whatever is added
     here is taken back off the top — the gap below the cue is roughly twice
     what this value moves it. Overridden in the stacked layout, where the
     section distributes its own slack instead. */
  margin-top: clamp(56px, 11.5vh, 132px);
  font-family: var(--mono);
  font-size: var(--t-micro);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-dim);
  text-decoration: none;
}

.cue:hover {
  color: #fff;
}

/* A shaft with a head, not a bare chevron: at 11px of tracked-out mono the
   arrowhead alone reads as punctuation. */
.cue__arrow {
  width: 12px;
  height: 34px;
  flex: 0 0 auto;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.1;
  stroke-linecap: round;
  stroke-linejoin: round;
  animation: sink 2.4s ease-in-out infinite;
}

@keyframes sink {
  0%,
  100% {
    transform: translateY(-3px);
    opacity: 0.34;
  }
  50% {
    transform: translateY(4px);
    opacity: 0.95;
  }
}

/* ------------------------------------------------------------------ */
/* Why — the four reasons                                               */
/* ------------------------------------------------------------------ */

/* Four across, then two and two, then one. Counted rather than fitted:
   `auto-fit` chose the column count from the available width, which at the
   sizes between those two states put three cards on the first row and left
   the fourth alone on the second. Four claims of equal weight should never
   be dealt 3:1. The switch to two columns is at 1180px, where the page also
   stops reserving the depth rail's full gutter, so the cards have room. */
.reasons {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  margin-top: 36px;
  background: rgba(152, 193, 217, 0.18);
  border: 1px solid rgba(152, 193, 217, 0.18);
  border-radius: 14px;
  overflow: hidden;
}

@media (max-width: 1180px) {
  .reasons {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 700px) {
  .reasons {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Two ink values in a card, not three: the icon sits at the heading's ink
   rather than taking the accent, which leaves the accent doing one job on
   this page — marking the section. The heading is the display face a weight
   below the section's own h2, so the two nest by weight instead of competing
   at the same one in different sizes. */
.reason {
  background: rgba(9, 24, 39, 0.28);
  padding: 28px 22px 30px;
}

.reason h3 {
  font-size: var(--t-sub);
  font-weight: 500;
  text-align: center;
  margin-top: 12px;
}

.reason p {
  margin-top: 15px;
  font-size: var(--t-body);
  color: var(--ink-dim);
}

/* Large enough to be read as drawn rather than as a glyph, which needs the
   stroke to come down as the box goes up — the 1.6 the markup carries reads
   as clip-art at this size. CSS wins over the presentation attribute. */
.reason svg {
  width: 56px;
  height: 56px;
  display: block;
  margin: 0 auto;
  stroke-width: 1.2;
  color: var(--ink);
}

/* One card is not four columns: at phone width the icon comes back to the
   heading's line and the copy hangs under the heading rather than the icon. */
@media (max-width: 700px) {
  .reason {
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: 12px;
    align-items: center;
    padding: 17px 18px 19px;
  }

  .reason h3 {
    text-align: left;
    margin-top: 0;
  }

  .reason p {
    grid-column: 2;
    margin-top: 6px;
  }

  .reason svg {
    width: 24px;
    height: 24px;
    stroke-width: 1.6;
    margin: 0;
  }
}

/* ------------------------------------------------------------------ */
/* Specimens — three shelves, three cards                               */
/* ------------------------------------------------------------------ */

.shelf {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(228px, 1fr));
  gap: clamp(24px, 3.4vw, 44px);
  margin-top: 40px;
  align-items: start;
}

/* Built by dive.js and hidden on the desktop grid, where there is nothing to
   page through. The dots stay on the page's own blue rather than taking the
   three shelf accents: ochre, wine and sage in a row of three read as a
   status light, not as a position. */
.shelf-nav {
  display: none;
}

.shelf-nav__dot {
  width: 9px;
  height: 9px;
  padding: 0;
  border: 1px solid rgba(152, 193, 217, 0.55);
  border-radius: 50%;
  background: none;
  cursor: pointer;
  transition:
    background-color 200ms ease,
    transform 200ms ease;
}

.shelf-nav__dot[aria-current='true'] {
  border-color: var(--sky);
  background: var(--sky);
  transform: scale(1.25);
}

/* One shelf at a time on a phone. Stacked, the three cards ran to something
   like three screens of scrolling for what is one idea; snapped sideways they
   are one screen, and the next card's edge showing past the gutter is the
   only affordance that says so. The strip is scrolled, so it is a tab stop —
   the global :focus-visible ring applies, and arrow keys move it. */
@media (max-width: 700px) {
  .shelf {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: 84%;
    align-items: stretch;
    gap: 12px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    /* Bleeds to both edges of the section and puts the gutter back as
       padding, so a card can sit flush left while the next one peeks. */
    margin-inline: calc(var(--gut) * -1);
    padding-inline: var(--gut);
    scroll-padding-inline: var(--gut);
    scrollbar-width: none;
  }

  .shelf::-webkit-scrollbar {
    display: none;
  }

  /* A card needs an edge here that it does not need on the desktop grid:
     borderless, the next card's sliver reads as clipped text rather than as
     somewhere to swipe. The hairline is the shelf's own accent, so the panel
     says which library it belongs to before the label does. */
  .shelf__item {
    scroll-snap-align: center;
    scroll-snap-stop: always;
    padding: 20px 18px 22px;
    border: 1px solid color-mix(in srgb, var(--accent) 32%, transparent);
    border-radius: 16px;
    background: rgba(9, 24, 39, 0.3);
  }

  /* The mockup is capped at 232px for the desktop column and would otherwise
     sit against the card's left edge with a hole beside it. */
  .shelf__item .phone {
    max-width: 100%;
    margin-inline: auto;
  }

  /* Cards stretch to the tallest, so the phones line up along one bottom
     edge however long each description runs. */
  .shelf__item .phone {
    margin-top: auto;
  }

  .shelf__line {
    margin-bottom: 18px;
  }

  /* Above the cards, where it reads as a control for what follows rather
     than a footnote to what came before. */
  .shelf-nav {
    display: flex;
    justify-content: center;
    gap: 9px;
    margin-top: 28px;
  }

  .shelf {
    margin-top: 14px;
  }
}

/* The badge and the mockup are centred in the column; the description keeps
   its full width and its left edge, because centred body copy in a 230px
   column would break badly. */
.shelf__item {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.shelf__line {
  width: 100%;
}

/* A rule, not a box. A solid field of a saturated accent was the heaviest
   object on a band otherwise built from gradients and blur; the colour now
   marks the lockup along its bottom edge instead of filling it. The rule
   keeps the taxonomy hex exactly, and the two lines take the lit tint of it
   — see --ochre-lit for what that costs and why. */
.shelf__badge {
  align-self: stretch;
  padding: 0 0 10px;
  border-bottom: 2px solid var(--accent);
  color: var(--accent-lit);
  text-align: center;
}

.shelf__item h3 {
  font-family: var(--mono);
  font-weight: 500;
  /* Off the type scale by 1.5px, and deliberately: at --t-label the badge
     was a big number with a caption stuck to it. This is the size at which
     the two lines balance. */
  font-size: 15px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: inherit;
}

.shelf__count {
  margin-top: 2px;
  font-family: var(--mono);
  font-size: 26px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.03em;
  color: inherit;
  font-variant-numeric: tabular-nums;
}

.shelf__line {
  margin-top: 12px;
  font-size: var(--t-body);
  color: var(--ink-dim);
}

.shelf__item .phone {
  margin-top: 20px;
}

/* ------------------------------------------------------------------ */
/* Screen placeholders                                                  */
/* ------------------------------------------------------------------ */

/* PLACEHOLDER: every .phone is a CSS stand-in at the real proportions, not a
   screenshot. Replace with captures from the app before launch — the frame,
   the glow and the caption all stay. */
.phone {
  position: relative;
  width: 100%;
  max-width: 232px;
  border-radius: 24px;
  padding: 7px;
  background: linear-gradient(180deg, #22405c, #0e1c2b);
  box-shadow:
    0 22px 54px -24px rgba(0, 0, 0, 0.78),
    0 0 52px -18px var(--accent);
}

.phone__screen {
  border-radius: 18px;
  background: #f6f9fc;
  color: #0f2b42;
  overflow: hidden;
  aspect-ratio: 9 / 16.4;
  display: flex;
  flex-direction: column;
}

.phone__bar {
  height: 5px;
  background: var(--accent);
  flex: 0 0 auto;
}

.phone__body {
  padding: 13px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: 1;
  min-height: 0;
}

.phone__kicker {
  font-family: var(--mono);
  font-size: 8.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: inherit;
}

.phone__title {
  font-family: var(--display);
  font-weight: 700;
  font-size: 15px;
  line-height: 1.15;
}

.phone__mol {
  background: #fff;
  border: 1px solid #e2eaf1;
  border-radius: 11px;
  flex: 1 1 auto;
  min-height: 86px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 8px;
  color: var(--slate);
}

.phone__mol svg {
  width: 100%;
  height: 100%;
  max-height: 120px;
}

.phone__rows {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.phone__row {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  font-family: var(--mono);
  font-size: 8.5px;
  color: #5d7186;
  font-variant-numeric: tabular-nums;
}

.phone__row b {
  color: #0f2b42;
  font-weight: 500;
}

.phone__pill {
  align-self: flex-start;
  font-size: 8.5px;
  font-weight: 600;
  padding: 3px 8px;
  border-radius: 999px;
  color: #fff;
  background: var(--accent);
}

.caption {
  margin-top: 20px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-dim);
  opacity: 0.7;
  font-size: 11px;
}

/* ------------------------------------------------------------------ */
/* The swap, performed                                                  */
/* ------------------------------------------------------------------ */

/* The structure and the reasoning are one argument, so they sit side by side
   rather than as a picture with a caption under it. Below 820px there is no
   room for two columns and it goes back to a stack. */
.plate {
  margin-top: 38px;
  border: 1px solid rgba(152, 193, 217, 0.24);
  border-radius: 18px;
  background: rgba(152, 193, 217, 0.05);
  padding: 30px 30px 24px;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: clamp(26px, 4vw, 54px);
}

/* INVARIANT: the ratio matches the viewBox on #sym-acid and #sym-tetrazole in
   index.html, which .devtools/render-structures.ts writes — it prints the
   ratio to use each time it runs. Those two share one box on purpose: the
   pair crossfades in place, so the benzene has to land on the same pixels in
   both, and a box the wrong shape letterboxes the drawing. */
.plate__struct {
  flex: 0 0 auto;
  position: relative;
  width: clamp(190px, 32vw, 330px);
  aspect-ratio: 197 / 104;
  color: var(--ink);
}

.plate__struct svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.plate__cap {
  max-width: 52ch;
  font-size: var(--t-body);
  color: var(--ink-dim);
}

@media (max-width: 820px) {
  .plate {
    flex-direction: column;
    align-items: center;
    padding: 30px 26px 26px;
    gap: 22px;
  }

  .plate__cap {
    text-align: center;
  }
}

/* The swap is the caption's headline; the four notes under it are what the
   swap costs and buys, one claim per line. The list stays left-aligned even
   where the block is centred — centred bullets have no edge to read down. */
.plate__swap {
  font-size: var(--t-sub);
  color: var(--ink);
  font-weight: 600;
}

.plate__notes {
  display: grid;
  gap: 6px;
  margin: 14px 0 0;
  padding-left: 21px;
  text-align: left;
}

.plate__notes li::marker {
  color: var(--accent);
}

/* Two structures in the same box; one fades out as the other fades in, on a
   7 s cycle with a long hold on each so it reads as a statement rather than
   a flicker. */
@keyframes hold-a {
  0%,
  44% {
    opacity: 1;
  }
  52%,
  94% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

@keyframes hold-b {
  0%,
  44% {
    opacity: 0;
  }
  52%,
  94% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}

.swap-a {
  animation: hold-a 7s ease-in-out infinite;
}

.swap-b {
  opacity: 0;
  animation: hold-b 7s ease-in-out infinite;
}

/* ------------------------------------------------------------------ */
/* The daily dive — four steps                                          */
/* ------------------------------------------------------------------ */

.steps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(196px, 1fr));
  gap: 24px;
  margin-top: 38px;
  counter-reset: step;
}

.step {
  counter-increment: step;
  border-top: 1px solid rgba(152, 193, 217, 0.3);
  padding-top: 15px;
}

/* The number sits in a chip on the heading's line: it is part of the step's
   name, not a label above it. Both take the mono face — this block and the
   library shelves are the page's two enumerated lists, and they read as a
   pair. The heading takes the lightened plum because the accent itself is
   2.6:1 on this band; see --plum-text. */
.step h3 {
  display: flex;
  align-items: center;
  gap: 11px;
  font-family: var(--mono);
  font-weight: 500;
  font-size: var(--t-sub);
  letter-spacing: 0.01em;
  color: var(--accent-text);
}

.step h3::before {
  content: counter(step, decimal-leading-zero);
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--plum);
  color: #fff;
  font-size: var(--t-micro);
  letter-spacing: 0.02em;
}

.step p {
  margin-top: 7px;
  font-size: var(--t-body);
  color: var(--ink-dim);
}

/* ------------------------------------------------------------------ */
/* The floor                                                            */
/* ------------------------------------------------------------------ */

/* The claim on the left, what backs it on the right. The list used to be six
   pills wrapping under the copy, where the eye counted shapes rather than
   reading claims. */
.seal {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
  gap: 30px clamp(30px, 4vw, 56px);
  align-items: start;
}

.nots {
  display: grid;
  gap: 15px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.nots li {
  display: flex;
  align-items: center;
  gap: 14px;
  font-family: var(--mono);
  /* These five are the section's argument, not a footnote to it, so they sit
     a step above label size — with tracking eased as the size goes up. */
  font-size: var(--t-body);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-dim);
}

/* The list's claims are about the app; this label says so, and the note under
   the list says what the website itself does. */
.nots__label {
  margin: 7px 0 15px;
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent-text, var(--ink-dim));
}

.nots__note {
  margin: 22px 0 0;
  max-width: 36ch;
  font-size: var(--t-micro);
  line-height: 1.6;
  color: var(--ink-dim);
}

/* The same lit bead the depth markers carry, at the same size — this section
   is about what is not there, so its list is marked by the page's own
   instrument rather than by another six borders. */
.nots li::before {
  content: '';
  flex: 0 0 auto;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--accent, var(--sky));
  box-shadow: 0 0 14px 1px var(--accent, var(--sky));
}

@media (max-width: 820px) {
  .seal {
    grid-template-columns: minmax(0, 1fr);
    gap: 26px;
  }
}

/* ------------------------------------------------------------------ */
/* Questions                                                            */
/* ------------------------------------------------------------------ */

/* Four, as a plain list. An accordion hides answers from anyone skimming,
   and there are not enough of them to be worth hiding. The 400px minimum
   holds it to two columns, so four questions fill two rows rather than
   leaving one stranded alone on a third. */
/* Two columns read down, not across: questions one and two fill the left
   column before the right one starts, so the order on the page is the order
   in the markup however the eye travels.

   INVARIANT: two rows, and four questions. A fifth would open a third column
   rather than a third row — add one and this wants `grid-template-rows` set
   to the new count. */
.qs {
  display: grid;
  gap: 26px 44px;
}

@media (min-width: 1000px) {
  .qs {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-rows: repeat(2, auto);
    grid-auto-flow: column;
  }
}

.q h3 {
  font-size: var(--t-sub);
  font-family: var(--body);
  font-weight: 600;
  letter-spacing: 0;
}

.q p {
  margin-top: 7px;
  font-size: var(--t-body);
  color: var(--ink-dim);
}

/* ------------------------------------------------------------------ */
/* Foot                                                                 */
/* ------------------------------------------------------------------ */

.foot {
  position: relative;
  z-index: 2;
  background: var(--w-abyss);
  border-top: 1px solid rgba(152, 193, 217, 0.14);
  padding: 48px var(--gut) 44px;
  color: var(--ink-dim);
  font-size: var(--t-body);
}

/* Everything stacks on the left margin. Spread apart, the nav was pushed to
   the far right, which is the only part of the footer that reaches the depth
   rail — the padding-right rules beside .stratum reserve that gutter. */
/* The name, set rather than placed: the footer used to carry the banner PNG
   inverted, which never sat right against type. The mascot's outline stands
   beside it at the size of a capital, facing the word it belongs to. */
.foot__mark {
  display: flex;
  align-items: center;
  gap: 11px;
  font-family: var(--display);
  font-weight: 700;
  font-size: 21px;
  letter-spacing: -0.02em;
  color: var(--ink);
}

/* A file rather than inline markup: the shape never changes, and it is the
   same drawing on every page that wants it. assets/manta-mark.svg is drawn in
   --ink, the wordmark's own colour, so the two read as one object. */
.foot__manta {
  flex: 0 0 auto;
  width: 38px;
  height: auto;
}

/* Name, then the fine print it belongs to, then the link. The spacing says
   which is which: the sentence sits under the wordmark as its caption, and
   the link stands apart. */
.foot__inner {
  max-width: 1440px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.foot__mark {
  margin-bottom: 10px;
}

.foot__fine {
  margin-bottom: 26px;
}

.foot nav {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 22px;
}

.foot a {
  text-decoration: none;
  border-bottom: 1px solid rgba(152, 193, 217, 0.3);
  padding-bottom: 1px;
}

.foot a:hover {
  color: #fff;
}

.foot__fine {
  max-width: 60ch;
  font-size: 14.5px;
  opacity: 0.75;
}

/* ------------------------------------------------------------------ */
/* Enhancement layer                                                    */
/* ------------------------------------------------------------------ */

/* Reveals are opt-in from JS: `.js` goes on <html> in the head, so a page
   without JavaScript never has anything parked at opacity 0. */
.js .reveal {
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity 520ms ease,
    transform 520ms cubic-bezier(0.16, 1, 0.3, 1);
}

.js .reveal.is-in {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .js .reveal,
  .js .reveal.is-in {
    opacity: 1;
    transform: none;
    transition: none;
  }
  /* Most of this page's motion is a deliberate exception and keeps running:
     the light shafts and surface shimmer, which are slow, blurred and behind
     the copy — without them the hero is a flat blue panel rather than water;
     the marine snow, for the same reason at a smaller scale; the mascot's
     swim, which is gentle and confined to the hero; and the bioisostere
     crossfade, which is not decoration at all — a still frame of that plate
     shows one structure and loses the substitution it exists to explain.

     What stops is the one thing that moves against the reading: the cue
     arrow bouncing under the hero, which is in the corner of the eye the
     whole time the first screen is on show. Section reveals and smooth
     scrolling are also off, above. */
  .cue__arrow {
    animation: none;
  }
}
