/*
  Touch — public-web design system (v2, 2026-09-26).
  ================================================================
  The shared stylesheet behind every page on touchapp.app, including the
  404; the home page adds home.css. 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.

  CHROME COLOURS MOSTLY MIRROR THE APP. Radii, brand and surface values trace 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`. The site's own values are the ink
  below and the light-of-day bands in home.css (founder, 2026-11-02).

  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 DIVERGENCES FORCED BY ACCESSIBILITY. 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.
  Separately, by design: `--c-ink`, `--c-body` and `--c-muted` are the site's cool
  inks, each measured AA on canvas and sunken at their declarations below.

  THE WARM PATH. `peach` #FFB8A6 is the colour of "the other person's day" in
  the Journey diagrams and a ground in home.css. On the home page's Dusk and Night
  bands it also sets the time labels, measured AA there; never on a light ground.

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

  THE HOME PAGE has its own stylesheet, home.css ("A day, told once",
  2026-11-02). This file is the chrome, type, controls and the Station card
  it shares with every other page.
*/

/* ============================================================
   1. Fonts — see web/public/fonts/README.md
   ============================================================ */

/* Official Fontshare WOFF2 files, byte for byte (fonts/README.md). Their licence
   forbids subsetting or converting them, so never regenerate these.
   Boska sets display type; General Sans, the app's face, sets everything read. */
@font-face { font-family: 'General Sans'; font-style: normal; font-weight: 400; font-display: swap; src: url('fonts/GeneralSans-Regular.woff2') format('woff2'); }
@font-face { font-family: 'General Sans'; font-style: normal; font-weight: 500; font-display: swap; src: url('fonts/GeneralSans-Medium.woff2') format('woff2'); }
@font-face { font-family: 'General Sans'; font-style: normal; font-weight: 600 700; font-display: swap; src: url('fonts/GeneralSans-Semibold.woff2') format('woff2'); }
/* Sized down so a headline line still fits, unwrapped, before Boska lands or if it fails. */
@font-face { font-family: 'Boska fallback'; src: local('Georgia'), local('Noto Serif'), local('Times New Roman'); size-adjust: 82%; }
@font-face { font-family: 'Boska'; font-style: normal; font-weight: 400; font-display: swap; src: url('fonts/Boska-Regular.woff2') format('woff2'); }
@font-face { font-family: 'Boska'; font-style: italic; font-weight: 400; font-display: swap; src: url('fonts/Boska-Italic.woff2') format('woff2'); }
@font-face { font-family: 'Boska'; font-style: normal; font-weight: 500 700; font-display: swap; src: url('fonts/Boska-Medium.woff2') format('woff2'); }

/* ============================================================
   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: #16141c; /* the site ink, 18.1:1 on white (2026-11-02) */
  --c-body: #47434f; /* 9.3:1 on white */
  --c-muted: #5f5b67; /* 6.6:1 on white, 5.6:1 on sunken */

  /* -- 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 */

  --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: 'Boska', 'Boska fallback', Georgia, serif;
  --f-body: 'General Sans', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --f-label: 'General Sans', 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).
  */
  --r-xs: 6px;
  --r-md: 14px;
  --r-section: 16px;
  --r-surface: 20px;

  /* -- 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; /* the reading measure; 68ch ran too long */
  --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;

    --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;
}

.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;
  text-decoration: none;
  color: var(--c-ink);
}

/*
  The supplied horizontal logo (brand/touch-logo-horizontal.svg), one image —
  never the symbol plus a typed "touch". Dark mode swaps in the founder's
  dedicated dark-mode master (brand/touch-logo-horizontal-dark.svg), as supplied.

  --logo-h is the box height. The artwork keeps its clear space: the logo's
  sizing frame (brand/README.md) is 56.25 of the 124 units, starting 86 units in,
  in both masters, so the box is sized for the frame and pulled left by that clear
  space to put the ink on the text column. 2026-09-26 (founder): the brand
  read as timid at 22px beside an 80px headline — the logo is 30px tall in
  the header (26px on phones, so the nav keeps one row at default text; the
  home page lets it wrap when browser text is enlarged) and largest in the
  footer, where nothing competes. Every artwork since keeps those frame heights.
*/
.lockup__logo {
  --logo-h: calc(30px * 124 / 56.25);
  display: block;
  flex: none;
  width: auto;
  /* Never the global img max-width: a clamped box shrinks the artwork. */
  max-width: none;
  height: var(--logo-h);
  aspect-ratio: 399 / 124;
  margin-inline-start: calc(var(--logo-h) * -86 / 124);
}

.site-footer .lockup__logo {
  --logo-h: calc(40px * 124 / 56.25);
}

@media (max-width: 620px) {
  .lockup__logo {
    --logo-h: calc(26px * 124 / 56.25);
  }

  .site-footer .lockup__logo {
    --logo-h: calc(34px * 124 / 56.25);
  }
}

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

/*
  Under 345px the lockup and the nav no longer fit one row (BL-205). The nav
  takes its room from the artwork's right clear space, as the home page does
  under 359px: the ink, the logo's size, the links and their 44px targets stay.
*/
@media (max-width: 345px) {
  .site-header .lockup {
    margin-right: -34px;
  }
}

/* ============================================================
   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-body);
  font-weight: 400;
  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);
}

/*
  The OpenStreetMap (ODbL) credit (BL-169): one quiet line on every page, above
  the copyright. Its links are inline in running text (WCAG 2.5.8 inline
  exception), underlined so they read as links in the muted colour.
*/
.site-footer__credit {
  margin: var(--s-3xl) 0 0;
  max-width: var(--w-prose);
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--c-muted);
}

.site-footer__credit a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
}

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

.site-footer__credit + .site-footer__base {
  margin-top: var(--s-sm);
}

/* ============================================================
   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);
}

/* Solid ink, inverting in dark, the same as the home page's call to action. */
.btn--primary {
  background: var(--c-ink);
  color: var(--c-canvas);
}

.btn--primary:hover {
  color: var(--c-canvas);
  opacity: 0.88;
}

.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);
  }
}

/* ============================================================
   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 as plain DISCS, never faces. The app's Journey timeline fills those
   discs with blurred, generated portraits of no one (founder exception FO-003,
   docs/product/ux-principles.md §10). This page deliberately does not: on a
   public page a face would read as a real Touch user.
*/
.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);
}

/* ============================================================
   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);
  /* Not the display serif: its hairline @ nearly vanishes in the one string this page exists to show. */
  font-family: var(--f-body);
  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;
}

/* A sub-list (privacy §4 "Photos of places") sits inside its item. */
.prose li > ul {
  margin: 0.4em 0 0;
}

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

/*
  The generated ODbL method on data-sources.html (BL-169). Its rules carry
  long unbroken tokens (64-character hashes, tag lists, a regular
  expression), which break anywhere rather than scroll the page sideways.
*/
.method h3 {
  font-size: 18px;
  line-height: 1.35;
  margin: var(--s-xl) 0 var(--s-sm);
}

.method li,
.method-version {
  overflow-wrap: anywhere;
}

.method-version {
  color: var(--c-muted);
  font-size: 14px;
}

/*
  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
   ============================================================

   The storytelling motion lives in home.css with the page it serves. Shared
   pages have feedback only (buttons, nav underline).

   Reduced motion removes MOVEMENT, not feedback. 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;
  }
}
