/*
  Touch — public-web design system (v2, 2026-09-26).
  ================================================================
  The single stylesheet behind every page on touchapp.app, including the
  404. Design knowledge — WHY it looks like this — lives in
  docs/design/public-web-design.md and docs/design/touch-design-principles.md.
  This header only records the constraints a code change can break.

  TOKENS ARE MIRRORED FROM THE APP, NOT INVENTED HERE.
  Every colour and radius below traces to
  `mobile/lib/core/design/design_tokens.dart`; the Station card replica
  (§8) traces to `mobile/lib/core/widgets/station_card.dart` via its mirror
  `design-system/components/station-card.html`. Do not add a colour the app
  does not have.

  THE CANVAS IS WHITE (2026-09-26, founder). It was the app's warm off-white;
  the app's redesigned screens and the Station card ("cards float on the
  white surface", station_card.dart) now sit on softWhite #FFFFFF, and so
  does the site. Raised surfaces on it are therefore separated by elevation
  or a hairline, never by fill alone (a white card on white vanishes).
  THE DELIBERATE DIVERGENCES, all accessibility-forced. Measured against the
  light surfaces (canvas and raised #FFFFFF / sunken #F1ECE3):
    - `bluePrimary` #6468C8 as LINK TEXT scores 4.82 / 4.10 — fails AA
      on sunken. `--c-link` is #5458BC (6.00 / 5.10). #6468C8 stays as
      a FILL (`--c-brand`), where white-on-it is 4.82.
    - `duskLavender` #8B80AB scores 3.40. `--c-eyebrow` is #6F6490.
    - `stone500` #736B5B is 5.3:1 on white but 4.4 on sunken; `--c-muted`
      is #665E50 (6.4:1 white, 5.4 sunken).

  THE WARM PATH. `peach` #FFB8A6 is the colour of "the other person's day" in
  the Journey diagrams. It is decorative (every diagram is aria-hidden and
  restated in text), so the 3:1 non-text rule does not bind it; it is never
  used for text.

  NO CDN, BY DESIGN. Fonts are self-hosted subsets. The public site makes
  ZERO third-party requests, and that is load-bearing, not incidental.
  `web/tools/qa.cjs` fails on any foreign request.

  MOTION. Every animation is CSS, transform/opacity only, and sits behind
  `prefers-reduced-motion: no-preference`. The two storytelling figures (the
  day line, the crossing) play ONCE, in time, when home.js adds `.is-playing`;
  they are hidden (`.is-armed`) only while off-screen, so no JS means the
  finished composition — never a half-drawn one. Never scroll-scrub a story:
  it freezes half-drawn when the reader pauses (2026-09-26).
*/

/* ============================================================
   1. Fonts — see web/public/fonts/README.md for the subset recipe
   ============================================================ */

/* Wordmark only. */
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/sora-600.woff2') format('woff2');
}
/* Small lowercase labels (the app's brand lead-in face). */
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/nunito-500.woff2') format('woff2');
}
/* Editorial display and headings. */
@font-face {
  font-family: 'Montserrat';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/montserrat-600.woff2') format('woff2');
}
@font-face {
  font-family: 'Montserrat';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('fonts/montserrat-700.woff2') format('woff2');
}
/* Body, UI and numerics. */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('fonts/inter-var.woff2') format('woff2-variations');
}

/* ============================================================
   2. Tokens
   ============================================================ */

:root {
  color-scheme: light dark;

  /* -- Surfaces -- */
  --c-canvas: #ffffff; /* softWhite — the app's current screen surface (founder, 2026-09-26) */
  --c-raised: #ffffff; /* softWhite    */
  --c-sunken: #f1ece3; /* warmCloud    */
  --c-line: #e5dfd3; /* sand100      */
  --c-line-strong: #d8d0c0;

  /* -- Ink -- */
  --c-ink: #2c2a26; /* stone800 — 14.3:1 on white */
  --c-body: #5a5448; /* stone600 —  7.5:1          */
  --c-muted: #665e50; /* stone500, darkened        */

  /* -- Brand -- */
  --c-brand: #6468c8; /* bluePrimary — FILLS ONLY   */
  --c-brand-warm: #666ac9; /* bluePrimaryWarm, darkened: white 4.70 (app value 4.49 fails AA) */
  --c-link: #5458bc; /* darkened for AA link text  */
  --c-eyebrow: #6f6490; /* darkened duskLavender      */
  --c-periwinkle: #afbbf2;
  --c-periwinkle-line: #c9d0f0; /* the app's timeline spine */
  --c-accent-mark: #6468c8; /* graphic accent, flips in dark */
  --c-wash: #eef1fb; /* periwinkleWash             */
  --c-station-halo: #dce1f9; /* the Station node's halo: periwinkleWash read on the white canvas */
  --c-on-brand: #ffffff;

  /* -- Warmth: the other person's path, and the moment paths meet -- */
  --c-peach: #ffb8a6; /* peach       */
  --c-peach-soft: #fbe6de; /* peachSoft   */
  --c-tile-warm: #ffeee6; /* StationCard recent tile */
  --c-tile-warm-ink: #e8825f; /* StationCard recent glyph */

  /* -- The one colour-block band (privacy) -- */
  --c-block: #24222a;
  --c-block-ink: #f2efea;
  --c-block-body: #c4bdb2;
  --c-block-line: #3a3640;
  --c-block-accent: #afbbf2;

  --c-focus: #5458bc;
  --c-selection: #e4e8fb; /* blueSoft */

  /* Destructive (TouchColors.dangerRed) — only the account-deletion action. */
  --c-danger: #ad4f4a; /* white on it: 5.26 */
  --c-danger-text: #ad4f4a; /* on canvas: 4.92 */

  /* -- Type -- */
  --f-display: 'Montserrat', 'Segoe UI', system-ui, -apple-system, sans-serif;
  --f-body: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --f-word: 'Sora', 'Montserrat', system-ui, sans-serif;
  --f-label: 'Nunito', 'Inter', system-ui, sans-serif;

  /* Type scale — fluid, capped. The hero is the only display-size line. */
  --t-hero: clamp(46px, 6.1vw, 80px);
  --t-h2: clamp(30px, 4.2vw, 50px);
  --t-h3: clamp(20px, 2vw, 23px);
  --t-lede: clamp(19px, 1.7vw, 22px);
  --t-body: 17px;
  --t-small: 14.5px;
  --t-micro: 12.5px;

  /* -- Space (TouchSpacing) -- */
  --s-xs: 4px;
  --s-sm: 8px;
  --s-md: 12px;
  --s-lg: 16px;
  --s-xl: 24px;
  --s-2xl: 32px;
  --s-3xl: 48px;
  --s-4xl: 64px;
  --s-5xl: 96px;
  --s-band: clamp(80px, 12vw, 160px);
  --s-gutter: clamp(20px, 5vw, 48px);

  /*
    Radius hierarchy (TouchRadius). One rule per role, never "everything 24":
      tag/focus 6 · control (button) 14 · note/table 16 · product surface 20
      (the Station card's own radius) · pill only for the status line.
  */
  --r-xs: 6px;
  --r-md: 14px;
  --r-section: 16px;
  --r-surface: 20px;
  --r-pill: 999px;

  /* -- Elevation — tinted, never pure black; most surfaces get none -- */
  --e-1: 0 1px 2px rgba(44, 42, 38, 0.06);
  --e-card: 0 8px 20px rgba(70, 60, 110, 0.06), 0 1px 3px rgba(70, 60, 110, 0.04);
  --e-lift: 0 18px 40px -12px rgba(70, 60, 110, 0.18), 0 2px 6px rgba(70, 60, 110, 0.06);

  /* -- Motion -- */
  --d-press: 120ms;
  --d-fast: 160ms;
  --d-base: 240ms;
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1); /* strong ease-out: arrivals land, hovers answer */
  --ease-path: cubic-bezier(0.45, 0.05, 0.25, 1); /* slow start, soft arrival */

  --w-page: 1200px;
  --w-prose: 62ch; /* ≈75–80 characters of Inter body text; 68ch ran to 99 */
  --h-header: 80px;
}

/*
  Dark theme. The app itself is light-only, so these are NOT inverted app
  tokens — inverting a warm cream palette produces a blue-grey corpse. The
  hues come from the one dark surface Touch already ships: /confirm's
  `prefers-color-scheme: dark` block in
  `backend/functions/src/auth/confirm_endpoint.ts`. Keep the two in agreement.
*/
@media (prefers-color-scheme: dark) {
  :root {
    --c-canvas: #17151a;
    --c-raised: #211e25;
    --c-sunken: #121016;
    --c-line: #322e38;
    --c-line-strong: #423d4a;

    --c-ink: #f2efea;
    --c-body: #b5aea4;
    --c-muted: #9e958a;

    --c-brand: #6468c8;
    --c-brand-warm: #666ac9;
    --c-link: #afbbf2; /* 9.7:1 on canvas */
    --c-eyebrow: #afbbf2;
    --c-accent-mark: #afbbf2; /* #6468C8 is only 2.9:1 on #17151A */
    --c-periwinkle-line: #3d3f63;
    --c-wash: #1e1c28;
    --c-station-halo: #2a2b45;
    --c-on-brand: #ffffff;

    --c-peach-soft: #2a211f;
    --c-tile-warm: #33251f;
    --c-tile-warm-ink: #ffb8a6;

    /* In dark mode the colour-block steps UP (a raised plane), not down:
       a darker band on a dark page disappears and the page loses its peak. */
    --c-block: #221f28;
    --c-block-line: #36323d;

    --c-focus: #afbbf2;
    --c-selection: #2f3054;

    --c-danger: #ad4f4a;
    --c-danger-text: #f0a39c; /* on #17151A: 9.0 */

    --e-1: 0 1px 2px rgba(0, 0, 0, 0.4);
    --e-card: 0 8px 22px rgba(0, 0, 0, 0.35), 0 1px 3px rgba(0, 0, 0, 0.3);
    --e-lift: 0 18px 44px -12px rgba(0, 0, 0, 0.6), 0 2px 6px rgba(0, 0, 0, 0.35);
  }
}

/* ============================================================
   3. Reset + base
   ============================================================ */

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

/* `hidden` must always win: component `display` rules (e.g. .btn) otherwise
   override the attribute, and a progressive control would show without JS. */
[hidden] {
  display: none !important;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--h-header) + 16px);
}

body {
  margin: 0;
  min-height: 100svh;
  background: var(--c-canvas);
  color: var(--c-body);
  font-family: var(--f-body);
  font-size: var(--t-body);
  line-height: 1.65;
  font-feature-settings: 'kern' 1, 'liga' 1;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-tap-highlight-color: transparent;
  display: flex;
  flex-direction: column;
  overflow-x: clip;
}

::selection {
  background: var(--c-selection);
  color: var(--c-ink);
}

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

h1,
h2,
h3,
h4 {
  font-family: var(--f-display);
  color: var(--c-ink);
  font-weight: 700;
  letter-spacing: -0.02em;
  text-wrap: balance;
  margin: 0;
}

p {
  margin: 0 0 1.1em;
  text-wrap: pretty;
}

p:last-child {
  margin-bottom: 0;
}

strong,
b {
  color: var(--c-ink);
  font-weight: 600;
}

a {
  color: var(--c-link);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  transition:
    color var(--d-fast) var(--ease-out),
    text-decoration-color var(--d-fast) var(--ease-out);
}

a:hover {
  color: var(--c-ink);
}

/* Never remove the ring — replace it with a better one. */
:focus-visible {
  outline: 2px solid var(--c-focus);
  outline-offset: 3px;
  border-radius: var(--r-xs);
}

:focus:not(:focus-visible) {
  outline: none;
}

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

/* ============================================================
   4. Layout
   ============================================================ */

.shell {
  width: 100%;
  max-width: var(--w-page);
  margin-inline: auto;
  padding-inline: var(--s-gutter);
}

main {
  flex: 1 0 auto;
}

.band {
  padding-block: var(--s-band);
}

/*
  Two bands on the same surface would stack two full paddings into a dead
  zone. Halve the seam — never zero it (zeroing is what glued the old site's
  eyebrows to the band above).
*/
.band:not(.block) + .band:not(.block),
.hero + .band:not(.block) {
  padding-top: calc(var(--s-band) * 0.5);
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--c-ink);
  color: var(--c-canvas);
  padding: 12px 18px;
  border-radius: 0 0 var(--r-md) 0;
  font-size: 15px;
  font-weight: 500;
}

.skip:focus {
  left: 0;
  color: var(--c-canvas);
}

/* ============================================================
   5. Brand lockup
   ============================================================ */

.lockup {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  text-decoration: none;
  color: var(--c-ink);
}

/*
  The mark is three paths, each ending in a node. Below ~20px of height the
  bars fuse and it reads as a hamburger icon — in the top-left corner, where
  a menu button lives. So the nav mark is never set smaller than 22px.
  2026-09-26 (founder): the brand read as timid at 22px beside an 80px
  headline. Header 30px (26px on phones, so the nav keeps one row); the
  footer carries it largest, where there is room and nothing competes.
*/
.lockup__mark {
  width: auto;
  height: 30px;
  flex: none;
  color: var(--c-accent-mark);
}

.lockup__word {
  font-family: var(--f-word);
  font-weight: 600;
  font-size: 31px;
  line-height: 1;
  letter-spacing: -0.025em;
}

.site-footer .lockup__mark {
  height: 40px;
}

.site-footer .lockup__word {
  font-size: 41px;
}

@media (max-width: 620px) {
  .lockup {
    gap: 10px;
  }

  .lockup__mark {
    height: 26px;
  }

  .lockup__word {
    font-size: 27px;
  }

  .site-footer .lockup__mark {
    height: 34px;
  }

  .site-footer .lockup__word {
    font-size: 35px;
  }
}

a.lockup {
  min-height: 44px;
}

a.lockup:hover {
  color: var(--c-ink);
}

a.lockup:hover .lockup__mark {
  color: var(--c-brand);
}

/* ============================================================
   6. Header / footer
   ============================================================ */

.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  background: color-mix(in srgb, var(--c-canvas) 86%, transparent);
  backdrop-filter: saturate(140%) blur(14px);
  -webkit-backdrop-filter: saturate(140%) blur(14px);
  border-bottom: 1px solid var(--c-line);
}

@supports not (backdrop-filter: blur(1px)) {
  .site-header {
    background: var(--c-canvas);
  }
}

/*
  The hairline appears only once content scrolls under the header. Pure CSS
  (scroll timeline) — no scroll listener. Browsers without scroll timelines
  keep the hairline permanently, which is the safe resting state.
*/
@supports (animation-timeline: scroll()) {
  .site-header {
    animation: header-edge linear both;
    animation-timeline: scroll(root);
    animation-range: 0 24px;
  }
  @keyframes header-edge {
    from {
      border-bottom-color: transparent;
    }
    to {
      border-bottom-color: var(--c-line);
    }
  }
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-lg);
  min-height: var(--h-header);
}

.site-nav {
  display: flex;
  align-items: center;
  gap: 2px;
}

.site-nav a {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 16px;
  color: var(--c-body);
  font-size: 17px; /* 15 → 17 (2026-09-26): in scale with the 30px mark */
  font-weight: 500;
  letter-spacing: -0.005em;
  text-decoration: none;
}

/* A short path-and-node underline — the mark's grammar, at nav scale. */
.site-nav a::after {
  content: '';
  position: absolute;
  left: 16px;
  right: 16px;
  bottom: 8px;
  height: 1.5px;
  border-radius: 2px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--d-base) var(--ease-out);
}

.site-nav a:hover {
  color: var(--c-ink);
}

.site-nav a[aria-current='page']::after {
  transform: scaleX(1);
}

/* Touch screens fire sticky hovers on tap; hover MOTION is for real pointers. */
@media (hover: hover) and (pointer: fine) {
  .site-nav a:hover::after {
    transform: scaleX(1);
  }
}

.site-nav a[aria-current='page'] {
  color: var(--c-ink);
}

.site-footer {
  flex: none;
  border-top: 1px solid var(--c-line);
  padding-block: var(--s-4xl) var(--s-2xl);
}

.site-footer__grid {
  display: grid;
  gap: var(--s-2xl);
}

@media (min-width: 820px) {
  .site-footer__grid {
    grid-template-columns: minmax(0, 1.4fr) repeat(3, minmax(0, 1fr));
    gap: var(--s-3xl);
  }
}

.site-footer__brand p {
  margin: var(--s-md) 0 0;
  font-family: var(--f-display);
  font-weight: 600;
  font-size: 15px;
  letter-spacing: -0.01em;
  color: var(--c-muted);
}

.site-footer__col h2 {
  font-family: var(--f-label);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: lowercase;
  color: var(--c-muted);
  margin: 0 0 var(--s-xs);
}

.site-footer__col ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.site-footer__col a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-size: 15px;
  color: var(--c-body);
  text-decoration: none;
}

.site-footer__col a:hover {
  color: var(--c-ink);
  text-decoration: underline;
}

.site-footer__base {
  margin-top: var(--s-3xl);
  font-size: 13.5px;
  color: var(--c-muted);
}

/* ============================================================
   7. Type roles + controls
   ============================================================ */

.label {
  display: block;
  font-family: var(--f-label);
  font-weight: 500;
  font-size: 14px;
  letter-spacing: 0.08em;
  text-transform: lowercase;
  color: var(--c-eyebrow);
  margin: 0 0 var(--s-lg);
}

.h2 {
  font-size: var(--t-h2);
  line-height: 1.08;
  letter-spacing: -0.032em;
}

.lede {
  font-size: var(--t-lede);
  line-height: 1.55;
  color: var(--c-body);
}

.muted {
  color: var(--c-muted);
}

.small {
  font-size: var(--t-small);
  line-height: 1.6;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-sm);
  min-height: 52px;
  padding: 14px 26px;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  font-family: var(--f-body);
  font-size: 16px;
  font-weight: 500;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  touch-action: manipulation;
  transition:
    transform var(--d-press) var(--ease-out),
    box-shadow var(--d-base) var(--ease-out),
    background-color var(--d-fast) var(--ease-out),
    border-color var(--d-fast) var(--ease-out),
    color var(--d-fast) var(--ease-out);
}

.btn:active {
  transform: scale(0.98);
}

/* Filled primary keeps the app's gradient (TouchSemantic.gradientPrimary). */
.btn--primary {
  background-image: linear-gradient(135deg, var(--c-brand-warm), var(--c-brand));
  color: var(--c-on-brand);
  box-shadow: var(--e-1);
}

.btn--primary:hover {
  color: var(--c-on-brand);
  box-shadow: var(--e-card);
}

.btn--quiet {
  background: transparent;
  border-color: var(--c-line-strong);
  color: var(--c-ink);
}

.btn--quiet:hover {
  background: var(--c-sunken);
  border-color: var(--c-muted);
  color: var(--c-ink);
}

.btn__arrow {
  width: 16px;
  height: 16px;
  transition: transform var(--d-base) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .btn:hover .btn__arrow {
    transform: translateX(3px);
  }
}

/*
  The pre-release state. Deliberately NOT a button: there is nothing to
  download yet. Swap the whole `.status` block for the store badge on the day
  the listing is live — nothing else on the page assumes the pre-release state.
  The dot is real semantic state (one per page), not decoration.
*/
.status {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 14px;
  font-size: 15px;
  color: var(--c-body);
}

/* Deliberately NOT pill-shaped: a bordered pill reads as a button, and this
   is a statement of state. The link beside it is the action. */
.status__state {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  color: var(--c-ink);
  font-weight: 500;
}

.status__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--c-accent-mark);
  flex: none;
}

.status a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-weight: 500;
}

/* ============================================================
   8. The Station card — a faithful replica of the app component
   ============================================================

   Source: mobile/lib/core/widgets/station_card.dart (mirror:
   design-system/components/station-card.html). Fixed 102px, 20px radius,
   70px category tile, place name as headline, time, area, and the avatar
   stack — which in the app is placeholder DISCS, never faces. Showing discs
   here is therefore not a stylisation; it is what the product shows.
*/
.station {
  display: grid;
  grid-template-columns: 70px minmax(0, 1fr) auto;
  align-items: center;
  gap: 14px;
  width: 100%;
  max-width: 360px;
  height: 102px;
  padding: 16px;
  background: var(--c-raised);
  border-radius: var(--r-surface);
  box-shadow: var(--e-card);
  text-align: left;
}

.station__tile {
  width: 70px;
  height: 70px;
  border-radius: 19px;
  background: var(--c-tile-warm);
  color: var(--c-tile-warm-ink);
  display: grid;
  place-items: center;
}

.station__tile svg {
  width: 26px;
  height: 26px;
}

/* A real place photo in the tile (the app's tile is a glyph stand-in today;
   station_card.dart reserves this slot for photos). */
.station__tile--photo {
  overflow: hidden;
  background: var(--c-sunken);
}

.station__tile--photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.station__place {
  display: block;
  font-family: var(--f-body);
  font-weight: 600;
  font-size: 15.5px;
  line-height: 1.25;
  color: var(--c-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.station__when,
.station__area {
  display: block;
  font-size: 13px;
  line-height: 1.5;
  color: var(--c-muted);
  white-space: nowrap;
}

.station__area {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
}

.station__area svg {
  width: 11px;
  height: 11px;
  flex: none;
}

.station__people {
  display: flex;
  align-items: center;
}

.station__disc {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--c-periwinkle), #8895e4);
  border: 2px solid var(--c-raised);
  box-shadow: 0 1px 4px rgba(60, 50, 88, 0.12);
}

.station__disc + .station__disc {
  margin-left: -10px;
}

.station--quiet .station__tile {
  background: var(--c-wash);
  color: var(--c-accent-mark);
}

/* ============================================================
   9. Home — hero (the moment, then the product)
   ============================================================ */

.hero {
  position: relative;
  padding-block: clamp(40px, 7vw, 96px) clamp(24px, 4vw, 56px);
}

.hero__grid {
  display: grid;
  gap: var(--s-3xl);
  align-items: center;
}

@media (min-width: 960px) {
  .hero__grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--s-2xl);
  }
}

.hero__title {
  font-size: var(--t-hero);
  line-height: 0.98;
  letter-spacing: -0.045em;
  margin: 0 0 var(--s-xl);
}

/* Two sentences, two lines, never re-broken: the product is both halves. */
.hero__title span {
  display: block;
  white-space: nowrap;
}

.hero__title span + span {
  color: var(--c-accent-mark);
}

.hero__lede {
  max-width: 30ch;
  margin-bottom: var(--s-2xl);
}

/*
  The moment, then the product. A photograph of ordinary life at a place,
  with the Station card resting on its lower edge: the card is what Touch
  makes of a place like this. On wide screens the frame sits in the right
  column and the card breaks out over its left edge, into the page, so the
  two read as one composition rather than an image with a sticker on it.
  On phones the card overlaps the bottom edge, centred.
*/
.hero-art {
  position: relative;
  width: 100%;
  max-width: 500px;
  margin-inline: auto;
  padding-bottom: 72px; /* room for the card that hangs below the frame */
}

.hero-art__frame {
  margin: 0;
  aspect-ratio: 4 / 5;
  border-radius: var(--r-surface);
  overflow: hidden;
  background: var(--c-sunken); /* holds the space before the image paints */
  box-shadow: var(--e-lift);
}

.hero-art__frame img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 30%;
}

.hero-art__card {
  position: absolute;
  left: 50%;
  bottom: 0;
  width: min(340px, 88%);
  transform: translateX(-50%);
}

.hero-art__card .station {
  box-shadow: var(--e-lift);
}

.hero-art__caption {
  margin: 10px 0 0;
  text-align: center;
  font-size: var(--t-micro);
  color: var(--c-muted);
}

@media (min-width: 960px) {
  /* The card crosses the frame's lower-left corner; its caption lands on the
     page below the photo, never on top of it. */
  .hero-art {
    margin-right: 0;
    padding-bottom: 56px;
  }

  .hero-art__card {
    left: -72px;
    bottom: 0;
    width: 340px;
    transform: none;
  }

  .hero-art__caption {
    text-align: left;
    padding-left: 4px;
  }
}

/* Tablet: one column, so the photo aligns to the copy's left edge rather
   than floating centred under left-aligned text. */
@media (min-width: 620px) and (max-width: 959px) {
  .hero-art {
    max-width: 440px;
    margin-inline: 0;
  }
}

/* A bright daylight photo glares on a dark page; settle it into the theme. */
@media (prefers-color-scheme: dark) {
  .hero-art__frame img {
    filter: brightness(0.86) saturate(0.95);
  }
}

/* ============================================================
   10. Home — the Journey (one line, one Station)
   ============================================================ */

.journey-head {
  margin-bottom: clamp(48px, 7vw, 88px);
}

.journey-head h2 {
  max-width: 14ch;
}

.journey-head p {
  max-width: 44ch;
  margin-top: var(--s-xl);
}

/*
  An ordered list, because a day IS ordered. Vertical on phones — exactly the
  app's timeline spine — and horizontal from 900px, where there is room for
  the day to read left-to-right like a sentence.
*/
.day {
  --stops: 5;
  list-style: none;
  margin: 0;
  padding: 0;
  position: relative;
  display: grid;
  gap: 0;
}

.day__stop {
  position: relative;
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr);
  column-gap: var(--s-lg);
  padding-bottom: var(--s-2xl);
}

/* The spine segment belonging to each stop. */
.day__stop::before {
  content: '';
  position: absolute;
  left: 13px;
  top: 14px;
  bottom: -14px;
  width: 2px;
  border-radius: 2px;
  background: var(--c-periwinkle-line);
}

.day__stop:last-child::before {
  display: none;
}

.day__node {
  position: relative;
  z-index: 1;
  width: 14px;
  height: 14px;
  margin: 7px 0 0 7px;
  border-radius: 50%;
  background: var(--c-canvas);
  border: 2px solid var(--c-line-strong);
}

.day__time {
  display: block;
  font-size: var(--t-micro);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  color: var(--c-muted);
}

.day__place {
  display: block;
  font-size: 16px;
  color: var(--c-body);
}

.day__stop--station .day__node {
  width: 22px;
  height: 22px;
  margin: 3px 0 0 3px;
  background: transparent;
  border: 0;
}

/* The Station's fill and its halo are separate layers so the halo can settle
   after the node lands (the one emphasis beat in the day). */
.day__stop--station .day__node::before,
.day__stop--station .day__node::after {
  content: '';
  position: absolute;
  border-radius: 50%;
}

.day__stop--station .day__node::before {
  inset: 0;
  background: var(--c-accent-mark);
}

.day__stop--station .day__node::after {
  inset: -6px;
  z-index: -1;
  background: var(--c-station-halo);
}

.day__stop--station .day__place {
  color: var(--c-ink);
  font-weight: 600;
}

.day__detail {
  grid-column: 2;
  margin-top: var(--s-lg);
}

.day__note {
  grid-column: 2;
  margin: var(--s-lg) 0 0;
  max-width: 40ch;
  font-size: 16px;
}

.day__note b {
  display: block;
  font-family: var(--f-display);
  font-size: var(--t-h3);
  letter-spacing: -0.02em;
  line-height: 1.25;
  margin-bottom: 6px;
}

@media (min-width: 900px) {
  .day {
    grid-template-columns: repeat(var(--stops), minmax(0, 1fr));
    column-gap: var(--s-xl);
  }

  .day__stop {
    grid-template-columns: 1fr;
    grid-template-rows: 28px auto;
    padding-bottom: 0;
    align-content: start;
  }

  /* The line runs horizontally, from this node to the next. */
  .day__stop::before {
    left: 14px;
    right: calc(-1 * var(--s-xl) - 14px);
    top: 13px;
    bottom: auto;
    width: auto;
    height: 2px;
  }

  .day__node {
    margin: 7px 0 0 7px;
  }

  .day__stop--station .day__node {
    margin: 3px 0 0 3px;
  }

  .day__label {
    margin-top: var(--s-md);
  }

  .day__detail,
  .day__note {
    grid-column: 1;
  }

  /* The station's detail and note may be wider than one column. */
  .day__stop--station .day__detail,
  .day__stop--station .day__note {
    width: max(330px, 200%);
  }

  .day__first .day__note {
    width: 200%;
  }
}

/* ============================================================
   11. Home — the meeting (possibility + choice)
   ============================================================

   An offset stair: a wide statement, then the explanation stepping in
   under its right half. The only section built this way, on purpose.
*/

.meeting {
  display: grid;
  gap: var(--s-2xl);
}

.meeting__title {
  font-size: clamp(34px, 5.4vw, 68px);
  line-height: 1.04;
  letter-spacing: -0.04em;
  max-width: 15ch;
}

.meeting__text p {
  max-width: 42ch;
}

@media (min-width: 900px) {
  .meeting {
    grid-template-columns: repeat(12, minmax(0, 1fr));
    column-gap: var(--s-xl);
  }

  .meeting__title {
    grid-column: 1 / span 10;
  }

  .meeting__text {
    grid-column: 7 / span 6;
    grid-row: 2;
    align-self: center;
  }

  /* The crossing fills the left half that used to sit empty under the title. */
  .meeting .crossing {
    grid-column: 1 / span 6;
    grid-row: 2;
    max-width: 500px; /* keeps the faded path ends clear of the text column */
  }
}

/*
  The crossing (moved here from the hero on 2026-09-26, founder). Two days in
  the mark's grammar arrive from opposite sides at one place. A fixed-aspect
  field; the path ends fade in from nothing, so they may overhang the column.
*/
.crossing {
  width: 100%;
  max-width: 560px;
  aspect-ratio: 640 / 320;
}

.crossing > svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.path {
  fill: none;
  stroke-width: 2.25;
  stroke-linecap: round;
}

.path--mine {
  /* Fades in from nothing: a day does not begin at the edge of a figure. */
  stroke: url(#fade-mine);
}

.path--theirs {
  stroke: url(#fade-theirs);
}

.stop {
  stroke-width: 2;
  fill: var(--c-canvas);
}

.stop--mine {
  stroke: var(--c-accent-mark);
}

.stop--theirs {
  stroke: var(--c-peach);
}

.meet {
  fill: var(--c-accent-mark);
}

.meet-ring {
  fill: none;
  stroke: var(--c-peach);
  stroke-width: 2;
}

.path-label {
  font-family: var(--f-label);
  font-size: 16px;
  font-weight: 500;
  letter-spacing: 0.04em;
  fill: var(--c-muted);
}

.meet-label {
  font-family: var(--f-body);
  font-weight: 600;
  letter-spacing: 0;
  fill: var(--c-ink);
}

@media (max-width: 899px) {
  /* SVG text scales with the field; keep it readable on phones. */
  .path-label {
    font-size: 21px;
  }
}

/* ============================================================
   12. Home — privacy (the one colour-block)
   ============================================================ */

.block {
  background: var(--c-block);
  color: var(--c-block-body);
}

.block h2,
.block h3,
.block b {
  color: var(--c-block-ink);
}

.block a {
  color: var(--c-block-accent);
}

.block a:hover {
  color: var(--c-block-ink);
}

.block .label {
  color: var(--c-block-accent);
}

.block :focus-visible {
  outline-color: var(--c-block-accent);
}

.privacy {
  display: grid;
  gap: var(--s-4xl);
}

@media (min-width: 960px) {
  .privacy {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    gap: var(--s-5xl);
    align-items: start;
  }
}

.privacy__lead p {
  max-width: 38ch;
  margin-top: var(--s-xl);
}

.privacy__lead .btn {
  margin-top: var(--s-2xl);
  color: var(--c-block-ink);
  border-color: var(--c-block-line);
}

.privacy__lead .btn:hover {
  background: rgba(255, 255, 255, 0.06);
  border-color: var(--c-block-body);
  color: var(--c-block-ink);
}

.promises {
  margin: 0;
  display: grid;
  gap: 0;
}

@media (min-width: 620px) {
  .promises {
    grid-template-columns: 1fr 1fr;
    column-gap: var(--s-3xl);
  }
}

.promises div {
  padding-block: var(--s-xl);
  border-top: 1px solid var(--c-block-line);
}

.promises dt {
  font-family: var(--f-display);
  font-weight: 600;
  font-size: 19px;
  line-height: 1.3;
  letter-spacing: -0.015em;
  color: var(--c-block-ink);
  margin-bottom: var(--s-sm);
}

.promises dd {
  margin: 0;
  font-size: 15.5px;
  line-height: 1.6;
}

/* ============================================================
   13. Home — what Touch leaves out
   ============================================================ */

/* The closing below opens with its own hairline + padding; a full band
   bottom here stacked ~300px of nothing between the two. */
.band--tight-end {
  padding-bottom: calc(var(--s-band) * 0.5);
}

.leaves-out {
  max-width: 1000px;
}

.leaves-out h2 {
  margin-bottom: var(--s-2xl);
}

.refusals {
  list-style: none;
  margin: 0;
  padding: 0;
  font-family: var(--f-display);
  font-weight: 600;
  font-size: clamp(22px, 2.9vw, 36px);
  line-height: 1.3;
  letter-spacing: -0.022em;
  color: var(--c-muted);
}

.refusals li {
  display: inline;
}

.refusals li + li::before {
  content: ' ';
}

.refusals em {
  font-style: normal;
  color: var(--c-ink);
}

/* ============================================================
   14. Home — closing
   ============================================================ */

.closing {
  display: grid;
  gap: var(--s-2xl);
  padding-top: var(--s-4xl);
  border-top: 1px solid var(--c-line);
}

@media (min-width: 900px) {
  .closing {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--s-5xl);
    align-items: start;
  }
}

.closing p {
  max-width: 44ch;
}

.closing .h2 {
  max-width: 12ch;
}

.closing .btn {
  margin-top: var(--s-xl);
}

/* ============================================================
   14b. Support — the contact card
   ============================================================ */

/*
  The address is the content, so it is set large and selectable; the buttons
  are conveniences around it. Held by a hairline (white on white).
*/
.contact-card {
  margin: 0 0 var(--s-2xl);
  padding: var(--s-xl);
  border: 1px solid var(--c-line);
  border-radius: var(--r-surface);
  box-shadow: var(--e-card);
}

.prose .contact-card p {
  margin: 0;
}

.prose .contact-card__label {
  font-size: var(--t-small);
  color: var(--c-muted);
}

.prose .contact-card .contact-card__address {
  margin: 4px 0 var(--s-lg);
  font-family: var(--f-display);
  font-weight: 600;
  font-size: clamp(22px, 4.6vw, 30px);
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: var(--c-ink);
  overflow-wrap: anywhere;
  user-select: all;
}

.contact-card__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-md);
}

.prose .contact-card .contact-card__status {
  min-height: 1.5em;
  margin-top: var(--s-sm);
  font-size: var(--t-small);
  color: var(--c-muted);
}

/* ============================================================
   15. Legal + utility pages
   ============================================================ */

.masthead {
  padding-block: clamp(48px, 8vw, 96px) clamp(32px, 4vw, 48px);
}

.masthead h1 {
  font-size: clamp(36px, 5.4vw, 60px);
  line-height: 1.04;
  letter-spacing: -0.038em;
  max-width: 16ch;
}

.masthead__meta {
  margin: var(--s-lg) 0 0;
  font-size: var(--t-small);
  color: var(--c-muted);
}

/*
  Legal layout: a reading column, with the contents list as a quiet sticky
  rail beside it on wide screens (Privacy Policy only). The rail is
  wayfinding, not navigation — it never competes with the text.
*/
.legal {
  display: grid;
  gap: var(--s-2xl);
  padding-bottom: var(--s-5xl);
}

@media (min-width: 1080px) {
  .legal--toc {
    grid-template-columns: 240px minmax(0, var(--w-prose));
    gap: var(--s-5xl);
    align-items: start;
  }
}

.toc {
  border-top: 1px solid var(--c-line);
  padding-top: var(--s-lg);
}

@media (min-width: 1080px) {
  .toc {
    position: sticky;
    top: calc(var(--h-header) + 24px);
    max-height: calc(100svh - var(--h-header) - 48px);
    overflow-y: auto;
  }
}

.toc h2 {
  font-family: var(--f-label);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: lowercase;
  color: var(--c-muted);
  margin: 0 0 var(--s-xs);
}

.toc ol {
  list-style: none;
  counter-reset: toc;
  margin: 0;
  padding: 0;
}

@media (min-width: 640px) and (max-width: 1079px) {
  .toc ol {
    columns: 2;
    column-gap: var(--s-2xl);
  }
}

.toc li {
  counter-increment: toc;
  break-inside: avoid;
}

.toc a {
  display: flex;
  gap: 10px;
  align-items: center;
  min-height: 44px;
  padding: 4px 0;
  font-size: 14.5px;
  line-height: 1.35;
  color: var(--c-body);
  text-decoration: none;
}

.toc a::before {
  content: counter(toc);
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  font-weight: 600;
  color: var(--c-eyebrow);
  min-width: 1.4em;
}

.toc a:hover {
  color: var(--c-ink);
}

.prose {
  max-width: var(--w-prose);
  min-width: 0;
}

.prose > h2 {
  font-size: clamp(22px, 2.4vw, 26px);
  line-height: 1.25;
  margin: var(--s-4xl) 0 var(--s-md);
  scroll-margin-top: calc(var(--h-header) + 24px);
}

.prose > h2:first-child,
.prose > h2.first {
  margin-top: 0;
}

.prose > h3 {
  font-size: 18px;
  line-height: 1.35;
  margin: var(--s-xl) 0 var(--s-sm);
}

.prose ul,
.prose ol {
  padding-left: 1.3em;
  margin: 0 0 1.1em;
}

.prose li {
  margin: 0.4em 0;
}

.prose li::marker {
  color: var(--c-muted);
}

/*
  The page's opening promise. It was a boxed card; it is now simply the
  document's lede — larger, darker, set off by a periwinkle rule — because a
  box around the first paragraph added a container, not hierarchy.
*/
.lede-card {
  margin: 0 0 var(--s-3xl);
  padding-left: var(--s-xl);
  border-left: 2px solid var(--c-periwinkle);
  font-size: 18.5px;
  line-height: 1.6;
  color: var(--c-ink);
}

.lede-card > :first-child {
  margin-top: 0;
}

.lede-card > :last-child {
  margin-bottom: 0;
}

.note {
  background: var(--c-sunken);
  border-radius: var(--r-section);
  padding: var(--s-lg) var(--s-xl);
  color: var(--c-body);
  font-size: 15.5px;
  line-height: 1.6;
  margin: var(--s-xl) 0;
}

/*
  Tables scroll inside themselves — the page body never scrolls sideways.
  On wide screens they may break out of the 68ch measure, rightward only
  (the TOC rail occupies the left).
*/
.table-wrap {
  overflow-x: auto;
  margin: var(--s-lg) 0 var(--s-xl);
  border: 1px solid var(--c-line);
  border-radius: var(--r-section);
  background: var(--c-raised);
  -webkit-overflow-scrolling: touch;
}

.table-wrap:focus-visible {
  outline-offset: 2px;
}

@media (min-width: 1280px) {
  .prose .table-wrap {
    width: min(900px, calc(100vw - 2 * var(--s-gutter) - 240px - var(--s-5xl)));
  }
}

.prose table {
  width: 100%;
  min-width: 460px;
  border-collapse: collapse;
  font-size: 15px;
  margin: 0;
}

.prose th,
.prose td {
  text-align: left;
  vertical-align: top;
  padding: 12px 16px;
  border-bottom: 1px solid var(--c-line);
}

.prose thead th {
  color: var(--c-muted);
  font-weight: 600;
  font-size: 12.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  background: var(--c-sunken);
  white-space: nowrap;
}

.prose tbody tr:last-child td {
  border-bottom: 0;
}

.prose td:first-child {
  font-weight: 600;
  color: var(--c-ink);
}

/*
  Numbered steps inside prose (the in-app deletion instructions).
  NOT a grid: `display:grid` on the <li> made every inline child its own grid
  item and shattered "Tap Delete account" into three stacked fragments. The
  counter is positioned out of flow instead.
*/
.prose ol.steps-list {
  counter-reset: step;
  list-style: none;
  padding-left: 0;
  margin: var(--s-lg) 0 var(--s-xl);
}

.prose ol.steps-list > li {
  counter-increment: step;
  position: relative;
  padding: 0 0 var(--s-lg) 44px;
  margin: 0;
}

/* A numbered node on a spine — the timeline grammar, used for a sequence. */
.prose ol.steps-list > li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 0;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--c-wash);
  color: var(--c-link);
  font-variant-numeric: tabular-nums;
  font-size: 13px;
  font-weight: 600;
  line-height: 28px;
  text-align: center;
}

.prose ol.steps-list > li::after {
  content: '';
  position: absolute;
  left: 13px;
  top: 32px;
  bottom: 4px;
  width: 2px;
  border-radius: 2px;
  background: var(--c-periwinkle-line);
}

.prose ol.steps-list > li:last-child::after {
  display: none;
}

/* ------------------------------------------------------------
   Web account deletion (BL-092) — the site's only form.
   ------------------------------------------------------------ */

[hidden] {
  display: none !important;
}

.verify {
  max-width: 460px;
  padding-bottom: var(--s-5xl);
}

.verify__step {
  display: grid;
  gap: var(--s-lg);
}

/* The grid gap is the rhythm; paragraph margins would stack on top of it. */
.verify__step > p,
.verify__step > ul {
  margin: 0;
}

/* A number read back for checking must never break across lines. */
.verify [data-bind='phone'] {
  white-space: nowrap;
}

.verify__title {
  font-size: clamp(22px, 2.4vw, 26px);
  line-height: 1.25;
}

.verify__lead {
  margin: 0;
}

.verify__list {
  margin: 0;
  padding-left: 1.2em;
}

.verify__list li + li {
  margin-top: 4px;
}

.verify__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-md);
}

.verify__aside {
  margin: 0;
}

.verify__recaptcha {
  margin-top: var(--s-3xl);
}

.field {
  display: grid;
  gap: var(--s-sm);
}

.field__label {
  font-weight: 600;
  font-size: 15px;
  color: var(--c-ink);
}

.field__row {
  display: flex;
  align-items: stretch;
}

.field__prefix {
  display: inline-flex;
  align-items: center;
  padding: 0 14px;
  border: 1px solid var(--c-line-strong);
  border-right: 0;
  border-radius: var(--r-md) 0 0 var(--r-md);
  background: var(--c-sunken);
  color: var(--c-body);
  font-variant-numeric: tabular-nums;
}

.field__input {
  width: 100%;
  min-height: 52px;
  padding: 12px 16px;
  border: 1px solid var(--c-line-strong);
  border-radius: var(--r-md);
  background: var(--c-raised);
  color: var(--c-ink);
  font: inherit;
  font-size: 17px; /* ≥16px: no iOS zoom on focus */
  font-variant-numeric: tabular-nums;
  transition: border-color var(--d-fast) var(--ease-out);
}

.field__row .field__input {
  border-radius: 0 var(--r-md) var(--r-md) 0;
}

.field__input::placeholder {
  color: var(--c-muted);
  opacity: 0.8;
}

.field__input:focus-visible {
  outline: 2px solid var(--c-focus);
  outline-offset: 2px;
  border-color: var(--c-focus);
}

.field__input--code {
  letter-spacing: 0.3em;
  font-size: 22px;
  max-width: 220px;
}

.field__hint {
  font-size: var(--t-small);
  line-height: 1.5;
  color: var(--c-muted);
}

.field__error {
  margin: 0;
  min-height: 0;
  font-size: 15px;
  line-height: 1.5;
  color: var(--c-danger-text);
}

.field__error:empty {
  display: none;
}

/* The one destructive control on the site: the app's own dangerRed. */
.btn--danger {
  background: var(--c-danger);
  color: #ffffff;
}

.btn--danger:hover {
  color: #ffffff;
  box-shadow: var(--e-card);
}

.btn:disabled {
  opacity: 0.55;
  cursor: progress;
}

.linklike {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--c-link);
  font: inherit;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  cursor: pointer;
}

.linklike:hover {
  color: var(--c-ink);
}

/* Utility page (404). */
.lost {
  display: grid;
  gap: var(--s-3xl);
  align-items: center;
  padding-block: clamp(56px, 10vw, 128px);
}

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

.lost h1 {
  font-size: clamp(36px, 5.4vw, 60px);
  line-height: 1.04;
  letter-spacing: -0.038em;
  margin-bottom: var(--s-xl);
}

.lost p {
  max-width: 40ch;
}

.lost__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-md);
  margin-top: var(--s-2xl);
}

.lost__links {
  align-items: center;
}

.lost__link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-inline: var(--s-sm);
}

.lost svg {
  width: 100%;
  max-width: 440px;
  height: auto;
  justify-self: center;
  overflow: visible;
}

/* ============================================================
   16. Motion
   ============================================================

   Two motions on the whole site, each with one job:
     1. The hero card — the Station card settles onto the photograph once.
        STORYTELLING: a place becomes a Station.
     3. The crossing (meeting section) — two days draw toward one place,
        once, when it scrolls into view. STORYTELLING: you were both there.
     2. The day (Journey) — one line travels the day once when it comes into
        view; the Station is the one pause. STORYTELLING: most of a day stays
        quiet, one place becomes a Station.
   Text is never delayed: headlines paint immediately.
*/

@media (prefers-reduced-motion: no-preference) {
  /* 1. The hero card settles onto the photograph once, after it paints. */
  .hero-art__card {
    opacity: 0;
    animation: card-in 0.6s var(--ease-out) 0.35s forwards;
  }

  @keyframes card-in {
    from {
      opacity: 0;
      translate: 0 12px;
    }
    to {
      opacity: 1;
      translate: 0 0;
    }
  }

  /*
    3. The crossing — the original hero choreography, restored as a one-shot
    that plays when the figure scrolls into view (home.js adds `.is-armed`
    while it is still off-screen, then `.is-playing` once it is well inside
    the viewport). Without JS it is never armed, so it shows finished.

    Two days draw toward each other on the soft-arrival curve (theirs 120ms
    behind), each stop pops as its line reaches it, then the meeting node
    lands, the ring settles once, and the place is named last.
  */
  .crossing.is-armed .path {
    stroke-dasharray: 1;
    stroke-dashoffset: 1;
  }

  .crossing.is-armed .stop,
  .crossing.is-armed .meet,
  .crossing.is-armed .meet-ring,
  .crossing.is-armed .meet-label {
    opacity: 0;
    transform-box: fill-box;
    transform-origin: center;
  }

  .crossing.is-playing .path {
    animation: draw 1.3s var(--ease-path) 0.1s forwards;
  }

  .crossing.is-playing .path--theirs {
    animation-delay: 0.22s;
  }

  .crossing.is-playing .stop {
    animation: node-in 0.45s var(--ease-out) var(--at, 0.5s) forwards;
  }

  .crossing.is-playing .meet {
    animation: node-in 0.45s var(--ease-out) 1.35s forwards;
  }

  /* The ring expands once and settles — an arrival, not a pulse. */
  .crossing.is-playing .meet-ring {
    animation: ring-settle 0.9s var(--ease-out) 1.42s forwards;
  }

  .crossing.is-playing .meet-label {
    animation: label-in 0.5s var(--ease-out) 1.62s forwards;
  }

  @keyframes draw {
    to {
      stroke-dashoffset: 0;
    }
  }

  @keyframes node-in {
    from {
      opacity: 0;
      transform: scale(0.6);
    }
    to {
      opacity: 1;
      transform: scale(1);
    }
  }

  @keyframes ring-settle {
    0% {
      opacity: 0;
      transform: scale(0.6);
    }
    45% {
      opacity: 1;
    }
    100% {
      opacity: 1;
      transform: scale(1);
    }
  }

  @keyframes label-in {
    from {
      opacity: 0;
      transform: translateY(4px);
    }
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }

  /*
    2. The day — plays once, in time, when it scrolls into view (home.js arms
    `.day` off-screen, then adds `.is-playing`). One line TRAVELS the day,
    stop to stop: each stop appears as the line reaches it; at the Station the
    line pauses, the node lands and its halo settles once; then the day runs
    on to its end. Soft start, steady middle, soft arrival. ~1.8s in total,
    the same tempo as the crossing. No JS / reduced motion = the finished day.
  */
  .day.is-armed .day__stop::before {
    transform: scaleY(0);
    transform-origin: top;
  }

  .day.is-armed .day__stop:not(.day__stop--station) .day__node,
  .day.is-armed .day__stop--station .day__node::before,
  .day.is-armed .day__stop--station .day__node::after {
    opacity: 0;
  }

  .day.is-playing .day__stop::before {
    animation: day-grow-y 0.3s linear forwards;
  }

  .day.is-playing .day__node {
    animation: node-in 0.4s var(--ease-out) forwards;
  }

  /* The sequence. Segment i leaves stop i; each node lands as the line arrives. */
  .day.is-playing .day__stop:nth-child(1) .day__node { animation-delay: 0s; }
  .day.is-playing .day__stop:nth-child(1)::before {
    animation-delay: 0.14s;
    animation-duration: 0.34s;
    animation-timing-function: cubic-bezier(0.45, 0.05, 0.9, 0.6); /* soft start */
  }
  .day.is-playing .day__stop:nth-child(2) .day__node { animation-delay: 0.44s; }
  .day.is-playing .day__stop:nth-child(2)::before { animation-delay: 0.48s; }

  /* The Station: the line pauses here. */
  .day.is-playing .day__stop--station .day__node { animation: none; }
  .day.is-playing .day__stop--station .day__node::before {
    animation: station-land 0.5s var(--ease-out) 0.76s forwards;
  }
  .day.is-playing .day__stop--station .day__node::after {
    animation: ring-settle 0.9s var(--ease-out) 0.88s forwards;
  }
  .day.is-playing .day__stop--station::before { animation-delay: 1.02s; }

  .day.is-playing .day__stop:nth-child(4) .day__node { animation-delay: 1.28s; }
  .day.is-playing .day__stop:nth-child(4)::before {
    animation-delay: 1.32s;
    animation-duration: 0.36s;
    animation-timing-function: cubic-bezier(0.1, 0.4, 0.3, 1); /* soft arrival */
  }
  .day.is-playing .day__stop:nth-child(5) .day__node { animation-delay: 1.62s; }

  @media (min-width: 900px) {
    .day.is-armed .day__stop::before {
      transform: scaleX(0);
      transform-origin: left;
    }

    .day.is-playing .day__stop::before {
      animation-name: day-grow-x;
    }
  }

  /* A Station arrives solid: opacity is done in the first third, scale lands. */
  @keyframes station-land {
    0% {
      opacity: 0;
      transform: scale(0.55);
    }
    35% {
      opacity: 1;
    }
    100% {
      opacity: 1;
      transform: scale(1);
    }
  }

  @keyframes day-grow-y {
    to {
      transform: scaleY(1);
    }
  }

  @keyframes day-grow-x {
    to {
      transform: scaleX(1);
    }
  }
}

/*
  Reduced motion removes MOVEMENT, not feedback (Emil: "fewer and gentler, not
  zero"). All storytelling motion above already sits behind no-preference, so
  it renders in its finished state here. What remains are colour and border
  transitions, which aid comprehension and stay; transforms are removed.
*/
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  .btn,
  .btn__arrow,
  .site-nav a::after {
    transition-property: color, background-color, border-color, box-shadow;
  }

  .btn:active,
  .btn:hover .btn__arrow {
    transform: none;
  }

  .site-nav a::after {
    transition: none;
  }
}

/* ============================================================
   17. Responsive fine-tuning + print
   ============================================================ */

/*
  The header must stay ONE row. In-page anchors drop below 620px — they only
  shortcut a scroll the reader is about to do anyway; nothing becomes
  unreachable. Height stays 44px; only horizontal padding gives.
*/
@media (max-width: 620px) {
  .site-nav a[href^='#'],
  .site-nav a[href^='/#'] {
    display: none;
  }

  /* 16px (was 14.5): in scale with the 26px phone mark. The one phone nav
     rule; an earlier duplicate was silently overridden by this one. */
  .site-nav a {
    padding: 0 12px;
    font-size: 16px;
  }

  .site-nav a::after {
    left: 12px;
    right: 12px;
  }

  :root {
    --h-header: 64px;
  }
}

@media (max-width: 560px) {
  body {
    font-size: 16.5px;
  }

  .btn {
    width: 100%;
  }

  .lede-card {
    padding-left: var(--s-lg);
    font-size: 17.5px;
  }
}

/*
  People genuinely print privacy policies — for their own records, and to
  hand to a DPO. Give them a document, not a screenshot of a website.
*/
@media print {
  :root {
    --c-canvas: #ffffff;
    --c-raised: #ffffff;
    --c-sunken: #ffffff;
    --c-ink: #000000;
    --c-body: #1a1a1a;
    --c-muted: #444444;
    --c-line: #cccccc;
  }

  .site-header,
  .site-footer__grid,
  .toc,
  .skip {
    display: none;
  }

  body {
    font-size: 11pt;
    line-height: 1.5;
  }

  .masthead,
  .legal {
    padding-block: 0;
  }

  .legal,
  .legal--toc {
    display: block;
  }

  .prose,
  .shell {
    max-width: none;
    padding-inline: 0;
  }

  .prose > h2 {
    break-after: avoid;
    margin-top: 20pt;
  }

  .table-wrap,
  .lede-card,
  .note {
    break-inside: avoid;
    box-shadow: none;
  }

  a {
    color: #000000;
    text-decoration: underline;
  }

  .prose a[href^='http']::after {
    content: ' (' attr(href) ')';
    font-size: 9pt;
    color: #444444;
    word-break: break-all;
  }
}
