/* ==========================================================================
   Encyclopedia Galactica — site styles

   Design tokens are lifted verbatim from the app so the marketing page and the
   product read as one system:
     mobile/lib/theme.ts   → lightColors / spacing / radius

   The page is light only, by choice. It commits to one theme rather than
   shipping a half-considered second one, so there are no theme media queries
   and no toggle — every color is declared once, on :root.

   Anything prefixed `eg-` is a 1:1 replica of a React Native component and is
   laid out in the app's own logical pixels. Do not "improve" those numbers —
   they have to match theme.ts, or the screenshots stop being screenshots.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
  /* The page commits to one theme. These are lightColors from
     mobile/lib/theme.ts, unchanged — the phone screens on this page are the
     app's real components, so the values have to be the app's real values. */
  color-scheme: light;

  --bg: #ffffff;
  --bg-elevated: #f5f5f5;
  --surface: #ebebeb;
  --surface-alt: #e0e0e0;
  --border: rgba(0, 0, 0, 0.08);
  --border-soft: rgba(0, 0, 0, 0.06);
  --text: #1a1a1a;
  --text-muted: #666666;
  --text-faint: #999999;
  --accent: #0e8a9b;
  --ready: #16a34a;
  --loading: #d97706;
  --error: #dc2626;
  --idle: #999999;
  --private-tint: #0e8a9b;
  --sidebar-text: #555555;
  --sidebar-text-active: #000000;
  --sidebar-bg-hover: rgba(0, 0, 0, 0.05);
  --sidebar-bg-active: rgba(0, 0, 0, 0.08);
  --input-border: rgba(0, 0, 0, 0.15);
  --pill-active-bg: rgba(0, 0, 0, 0.08);
  --pill-border: rgba(0, 0, 0, 0.08);
  --pill-active-border: rgba(0, 0, 0, 0.16);
  --error-bg: #fde8e8;

  /* --text-faint is the app's value and stays exact inside the phone replicas,
     but it misses AA at the sizes the page itself uses. Page chrome gets its
     own faint tone. */
  --site-faint: #6e6e6e;

  /* Slate, sampled from the logo's engraving. This is the atmosphere's ink and
     it stays neutral, so the accent is the only colour on the page carrying
     meaning. */
  --ink: #2f3b42;
  --ink-alt: #4a6379;

  /* Tints of the app's own accent. --accent above is lightColors.accent
     unchanged; these are only the shades a 2px marker, a wash and a glow need.
     Every mark the site makes — tab and nav markers, the progress line, the
     rule under the wordmark, the row highlight, the privacy packets — is
     therefore the same colour the app uses for signal. */
  /* The app's accent is 4.1:1 on white — under AA. Where the site sets it as
     text, or fills a surface that carries white text, it uses this deeper shade
     of the same hue instead, at 5.9:1. --accent itself is never altered: the
     phone replicas have to keep the app's exact value. */
  --accent-deep: #0a6f7d;
  --accent-mid: #3a9fb0;
  --accent-soft: #a5d5de;
  --accent-wash: rgba(14, 138, 155, 0.12);
  --accent-glow: rgba(14, 138, 155, 0.28);

  --ease: cubic-bezier(0.2, 0.7, 0.3, 1);

  /* spacing scale — theme.ts */
  --sp-xs: 4px;
  --sp-sm: 8px;
  --sp-md: 12px;
  --sp-lg: 16px;
  --sp-xl: 24px;
  --sp-xxl: 32px;

  /* radius scale — theme.ts */
  --r-sm: 6px;
  --r-md: 10px;
  --r-lg: 14px;
  --r-xl: 20px;
  --r-pill: 999px;

  /* The app never bundles a typeface (see mobile/README.md) — React Native
     draws it in the platform UI font: San Francisco on iOS, Roboto on Android.
     The page uses the same stack so the screens here render in the exact
     glyphs the app does, and nothing is fetched from a font CDN. */
  --font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, system-ui, sans-serif;
  --font-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;

  /* The floating bar's own height plus the gap above it. The page is pulled up
     by this so the hero's atmosphere starts at y=0 and runs behind the bar,
     rather than being cut off dead at the bar's lower edge. */
  --bar-h: 72px;

  --shell: 1180px;
  --gutter: clamp(20px, 5vw, 48px);
  --phone-scale: 0.7;
  --section-pad: clamp(72px, 11vh, 132px);
}

/* --------------------------------------------------------------------------
   2. Base
   -------------------------------------------------------------------------- */

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

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
  /* Clear the floating bar so nav jumps do not land underneath it. */
  scroll-padding-top: 92px;
}

body {
  margin: 0;
  /* Not --bg. The app paints every screen, the drawer and the input dock in
     bgElevated (mobile/app/_layout.tsx sets it as the navigator's
     contentStyle), so #f5f5f5 is the white the app actually shows. The
     replicas' .phone__screen already use it; the page around them has to be
     the same paper or the phone reads as a lighter object on a whiter one. */
  background: var(--bg-elevated);
  color: var(--text);
  font-family: var(--font);
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

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

[hidden] { display: none !important; }

a { color: inherit; }

::selection {
  background: var(--accent-deep);
  color: var(--bg);
}

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

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

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

/* --------------------------------------------------------------------------
   3. Typography roles
   Display, body and data all come from the platform's own UI family (SF and
   SF Mono on Apple devices, Roboto on Android) — the same faces the app is
   drawn in. They are separated by weight, size and tracking rather than by
   face — the same discipline the app uses.
   -------------------------------------------------------------------------- */

.eyebrow {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--site-faint);
  margin: 0;
}

.h-display {
  font-weight: 300;
  font-size: clamp(46px, 8.6vw, 96px);
  line-height: 0.98;
  letter-spacing: 0.055em;
  margin: 0;
  color: var(--text);
}

.h-section {
  font-weight: 400;
  font-size: clamp(27px, 3.6vw, 40px);
  line-height: 1.14;
  letter-spacing: 0.005em;
  margin: 0;
  color: var(--text);
}

.lede {
  font-size: clamp(17px, 1.5vw, 19px);
  line-height: 1.65;
  color: var(--text-muted);
  margin: 0;
  max-width: 46ch;
}

.mono, .spec__value, .fact__value {
  font-variant-numeric: tabular-nums;
}

.mono {
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.01em;
  font-variant-ligatures: none;
}

/* The 60x1 rule + tracked caption under the app's Welcome title. Reused here
   so the hero is recognisably the same object as the app's first screen. */
.rule {
  width: 60px;
  height: 1px;
  background: var(--border);
  border: 0;
  margin: 14px 0;
}
.rule--glint { background: linear-gradient(90deg, var(--ink), transparent); opacity: 0.5; }

.motto {
  font-size: 12px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0;
}

/* --------------------------------------------------------------------------
   4. Buttons — pill geometry from the app (radius.pill, hairline border)
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 13px 22px;
  border-radius: var(--r-pill);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 15px;
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
  transition: background 0.18s ease, border-color 0.18s ease, transform 0.18s ease;
}
.btn:hover { background: var(--surface-alt); border-color: var(--pill-active-border); }
.btn:active { transform: translateY(1px); }

.btn--primary {
  background: var(--text);
  color: var(--bg);
  border-color: var(--text);
}
.btn--primary:hover { background: var(--text); border-color: var(--text); opacity: 0.88; }

.btn--ghost { background: transparent; }
.btn--ghost:hover { background: var(--sidebar-bg-hover); }

.btn__icon { width: 17px; height: 17px; flex: none; }

/* --------------------------------------------------------------------------
   5. Site header
   -------------------------------------------------------------------------- */

/* The bar floats: it is inset from the viewport, carries a border on all four
   sides rather than only underneath, and lifts off the page on a soft shadow.
   .site-header is only the sticky positioner and is transparent, so content
   scrolls visibly through the gap above and beside the bar — which is what
   sells the detachment. It ignores pointer events so those gutters stay
   inert; the bar itself takes them back. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 60;
  padding-top: 12px;
  pointer-events: none;
}

.site-header__bar {
  position: relative;
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: var(--sp-lg);
  height: 60px;
  padding-inline: 18px;
  border: 1px solid var(--border);
  border-radius: 15px;
  background: color-mix(in srgb, var(--bg-elevated) 82%, transparent);
  backdrop-filter: saturate(160%) blur(16px);
  -webkit-backdrop-filter: saturate(160%) blur(16px);
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.04),
    0 10px 28px -10px rgba(0, 0, 0, 0.14);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-right: auto;
  text-decoration: none;
}
.brand__mark {
  width: 26px; height: 26px;
  border-radius: 13px;
  opacity: 0.9;
  flex: none;
}
.brand__name {
  font-size: 15px;
  letter-spacing: 0.03em;
  color: var(--text);
  white-space: nowrap;
}

/* The bar is 60px tall, so the CTA runs shorter than a body button: a full-size
   one leaves only 8px of air above and below and swamps the bar. Same label and
   same fill as the hero's primary action, so it reads as the same control. */
.site-header__cta {
  flex: none;
  padding: 9px 16px;
  font-size: 14px;
  gap: 7px;
}
.site-header__cta .btn__icon { width: 15px; height: 15px; }

.site-nav { display: flex; gap: 4px; }
.site-nav__link {
  position: relative;
  padding: 7px 11px;
  border-radius: var(--r-md);
  font-size: 14px;
  color: var(--text-muted);
  text-decoration: none;
  transition: color 0.2s var(--ease), background 0.16s ease;
  white-space: nowrap;
}
.site-nav__link:hover { color: var(--text); background: var(--sidebar-bg-hover); }

/* Which section you are in — an accent rule under the word. Inset to the
   padding so it sits under the label rather than the hit area, and kept well
   clear of the progress line along the bar's bottom edge, which answers "how
   far" rather than "where". */
.site-nav__link::after {
  content: "";
  position: absolute;
  left: 11px;
  right: 11px;
  bottom: 3px;
  height: 2px;
  border-radius: 1px;
  background: var(--accent-mid);
  opacity: 0;
  transform: scaleX(0);
  transform-origin: center;
  transition: transform 0.32s var(--ease), opacity 0.24s var(--ease);
}
.site-nav__link[aria-current] {
  color: var(--text);
  background: none;
}
.site-nav__link[aria-current]::after { opacity: 1; transform: scaleX(1); }

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

/* The wordmark and a full-length CTA do not both fit a phone-width bar at full
   size. The wordmark is the page's identity, so it is not the half that gives
   way — the button gives ground instead, in two steps.

   First the pair simply tightens. Then, on the narrowest phones, the button
   drops to its glyph alone: an Apple mark in a black pill is a understood
   "get the app", the hero repeats the button in full a few hundred pixels
   below, and aria-label carries the name for anyone not reading the shape. */
@media (max-width: 460px) {
  .brand__name { font-size: 14px; }
  .brand { gap: 8px; }
  .site-header__cta { padding: 8px 12px; font-size: 12.5px; gap: 6px; }
}

/* --------------------------------------------------------------------------
   6. Hero
   The hero is the app's Welcome screen at monitor scale: same stacked title,
   same 60px rule, same tracked motto — with a phone in front running it for
   real. Atmosphere comes from the logo's own stipple engraving, not from a
   gradient.
   -------------------------------------------------------------------------- */

main { margin-top: calc(var(--bar-h) * -1); }

.hero {
  position: relative;
  isolation: isolate;
  /* The bar's height is added back, so the content sits exactly where it did
     and only the painted background moves up. */
  padding-top: calc(clamp(56px, 9vh, 104px) + var(--bar-h));
  padding-bottom: clamp(56px, 9vh, 104px);
  border-bottom: 1px solid var(--border);
  overflow: hidden;
}

.outro__stars {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  opacity: 0.6;
  -webkit-mask-image: radial-gradient(ellipse at 50% 50%, transparent 18%, #000 72%);
  mask-image: radial-gradient(ellipse at 50% 50%, transparent 18%, #000 72%);
}

.hero__stars {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
}

.hero__inner {
  display: grid;
  gap: clamp(40px, 6vw, 64px);
  align-items: center;
}
.hero__inner > * { min-width: 0; }

.hero__copy {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.hero__title { margin-top: 20px; }
.hero__title .line { display: block; }

.hero__motto { margin-bottom: 26px; }

.hero__lede { margin-bottom: 30px; }

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 34px;
}

/* Three facts, hairline-separated — the same label/value rhythm as the app's
   Settings → About rows. */
.hero__facts {
  display: flex;
  flex-wrap: wrap;
  border-top: 1px solid var(--border);
  width: 100%;
  max-width: 520px;
}
.fact {
  padding: 16px 22px 4px 0;
  margin-right: 22px;
  border-right: 1px solid var(--border);
  flex: 1 1 auto;
}
.fact:last-child { border-right: 0; margin-right: 0; padding-right: 0; }
.fact__value {
  display: block;
  font-family: var(--font-mono);
  font-size: 19px;
  color: var(--text);
  letter-spacing: -0.01em;
}
.fact__label {
  display: block;
  margin-top: 3px;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--site-faint);
}

.hero__device {
  display: flex;
  justify-content: center;
  min-width: 0;
}


@media (min-width: 1000px) {
  .hero__inner { grid-template-columns: minmax(0, 1.02fr) minmax(0, 0.98fr); }
  .hero__device { justify-content: flex-end; }
}

/* --------------------------------------------------------------------------
   7. Sections
   Every section is opened by a question put to the app, drawn with the app's
   own query row, so the page reads as one conversation. The glyph in the
   square answers its own question rather than repeating the search mark —
   a phone for what it looks like, a struck-out cloud for where questions go,
   a target for the one subject it holds to, a chip for what is under the hood.
   -------------------------------------------------------------------------- */

.section {
  position: relative;
  padding-block: var(--section-pad);
  border-bottom: 1px solid var(--border);
}
.section:last-of-type { border-bottom: 0; }

/* The app's query row (28px surface square + search glyph + 18/26 medium
   text) enlarged to section-heading scale. */
.q-row {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  max-width: 780px;
}
.q-row__icon {
  width: 40px;
  height: 40px;
  border-radius: var(--r-lg);
  background: var(--surface);
  display: grid;
  place-items: center;
  flex: none;
  margin-top: 2px;
  color: var(--text-muted);
}
.q-row__icon svg { width: 19px; height: 19px; }
.q-row__text {
  font-size: clamp(24px, 3.2vw, 34px);
  font-weight: 500;
  line-height: 1.24;
  letter-spacing: 0.002em;
  margin: 0;
  color: var(--text);
}

.section__sub {
  margin: 20px 0 0;
  padding-left: 56px;
  max-width: 620px;
  font-size: 16px;
  line-height: 1.65;
  color: var(--text-muted);
}

.section__body { margin-top: clamp(36px, 5vw, 56px); }

/* --------------------------------------------------------------------------
   8. Screen tour — the screenshots
   One device, and the pills swap what is on it. A rail of seven equal phones
   gave a mostly-empty Welcome and Download the same billing as a screen doing
   real work, needed a "scroll for more" hint to be discoverable at all, and
   left the explanation as grey text under a 600px mockup. Here the active
   screen is the whole point and its explanation sits beside it.
   -------------------------------------------------------------------------- */

/* The strip used to be the History screen's filter pills at 1:1 — which put the
   site's control in the app's clothes, right beside a mockup of the app, so it
   read as part of the phone rather than as page chrome. Those pills still exist
   where they belong: inside the History screen.

   The labels keep the tracked micro-label voice of the eyebrows and the fact
   labels, and the selected one sits in a container: a soft wash of the app's
   accent with a hairline of the same, squared off enough that it is not one of
   the app's round pills. There is one container for the whole strip, not one
   per label — it slides to whichever label is picked (app.js measures the
   target and hands the position over in --marker-*), so switching reads as the
   marker travelling rather than one box blinking off and another on. */

.tabs {
  --tab-pad: 14px;
  position: relative;
  display: flex;
  flex-wrap: nowrap;
  gap: clamp(2px, 0.8vw, 8px);
  /* The container has padding the labels never had; pull the strip back by
     that much so the first label still lines up with the text above it. */
  margin-left: calc(var(--tab-pad) * -1);
}

.tab {
  position: relative;
  padding: 9px var(--tab-pad);
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--text-muted);
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  cursor: pointer;
  white-space: nowrap;
  transition: color 0.22s var(--ease);
}

/* Hover shows where a container would land — the app's own row-hover tint. */
.tab::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--sidebar-bg-hover);
  opacity: 0;
  transition: opacity 0.18s var(--ease);
}
.tab:hover { color: var(--text); }
.tab:hover::before { opacity: 1; }

.tab[aria-selected="true"] { color: var(--accent-deep); }
.tab[aria-selected="true"]:hover::before { opacity: 0; }

/* The travelling container. Sits under the labels and scrolls with them on
   phones, since it is positioned inside the strip rather than over it. */
.tabs__marker {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--marker-w, 0px);
  height: var(--marker-h, 100%);
  transform: translateX(var(--marker-x, 0px));
  border-radius: 8px;
  background: var(--accent-wash);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-mid) 35%, transparent);
  opacity: 0;
  pointer-events: none;
  transition: transform 0.36s var(--ease), width 0.36s var(--ease), opacity 0.22s var(--ease);
}
.tabs.has-marker .tabs__marker { opacity: 1; }
/* A re-measure after a resize or a font swap is a correction, not a move. */
.tabs.is-settling .tabs__marker { transition: none; }

/* Until the script has measured (or when there is no script at all), the
   selected label draws its own container so nothing is ever unmarked. */
.tabs:not(.has-marker) .tab[aria-selected="true"] {
  background: var(--accent-wash);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-mid) 35%, transparent);
}

.tour__stage {
  margin-top: clamp(28px, 4vw, 44px);
  /* The outgoing panel is parked on top of this while it fades. */
  position: relative;
}

.tour__panel {
  display: grid;
  gap: clamp(28px, 4vw, 56px);
  align-items: center;
  justify-items: center;
}
/* With no script every panel simply stacks, so all seven stay readable. With
   script only one is ever in flow, and the gap would otherwise drop the six
   later screens 72px below the first — a jump on the very first switch. */
html:not(.js) .tour__panel + .tour__panel { margin-top: clamp(40px, 6vw, 72px); }

@media (min-width: 900px) {
  .tour__panel {
    grid-template-columns: auto minmax(0, 1fr);
    justify-items: start;
    /* Top-aligned, not centred. A 640px device beside a 400px list centres to
       a gap above and below and reads as an accident; hung from the same line
       it reads as a spec sheet next to the thing it describes. */
    align-items: start;
    gap: clamp(40px, 6vw, 84px);
  }
  /* Down to the screen's top edge, clear of the bezel. */
  .tour__detail { padding-top: 24px; }
}

/* --- the anatomy panel ----------------------------------------------------
   The right column used to be a caption: a name, a sentence, and two facts,
   floated in the middle of a column as wide as the phone is tall. It is now a
   labelled anatomy of the screen beside it. Each row names a real element and,
   because these replicas are components rather than images, pointing at the
   row rings that exact element on the phone.

   The numbering is not decoration: the seven screens are a sequence you move
   through once, and within a panel the rows run top-to-bottom down the screen
   they describe. */

.tour__detail {
  max-width: 520px;
  width: 100%;
}

/* Position in the seven, then the screen's name. The tab strip already carries
   the name, so it is set small here — it earns its place only when the strip
   has scrolled out of view on a phone. */
.tour__index {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 0 0 16px;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.04em;
  color: var(--site-faint);
}
.tour__num {
  font-size: 15px;
  font-weight: 500;
  color: var(--accent-deep);
}
.tour__label {
  margin-left: 6px;
  font-family: var(--font);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text);
}

/* The description is the panel's heading — the tab supplies the short name, so
   repeating it in display type would say the same thing twice. */
.tour__desc {
  margin: 0;
  font-size: clamp(18px, 1.6vw, 21px);
  line-height: 1.5;
  color: var(--text);
  max-width: 36ch;
}

.tour__anno {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--border);
}
.anno {
  position: relative;
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr) auto;
  align-items: center;
  gap: 0 14px;
  padding: 13px 0;
  border-bottom: 1px solid var(--border);
  outline: none;
}

/* The tint that marks the pointed-at row. Bled past the text on both sides so
   it reads as a band rather than a box, and drawn behind the row rather than as
   a background so the hairline rules keep their own width. It also closes the
   long gap between a short label and its right-aligned value: the two sit in
   one lit field instead of drifting apart. */
.anno::before {
  content: "";
  position: absolute;
  inset: 1px -14px;
  z-index: -1;
  border-radius: 8px;
  background: var(--accent-wash);
  opacity: 0;
  transition: opacity 0.2s var(--ease);
}
.anno__num {
  width: 21px;
  height: 21px;
  display: grid;
  place-items: center;
  border: 1px solid var(--border);
  border-radius: 50%;
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1;
  color: var(--site-faint);
  transition: color 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}
.anno__label {
  font-size: 15px;
  font-weight: 500;
  color: var(--text);
}
.anno__value {
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--text-muted);
  text-align: right;
  transition: color 0.2s ease;
}

/* Inert at rest — an outline takes no layout space and this one is transparent,
   so the replicas are unchanged until you point at their row. */
[data-spot] {
  /* 3px, not 2: the device is drawn at 0.7 scale, so a 2px ring lands at 1.4px
     and reads as a hairline rather than as a mark. */
  outline: 3px solid transparent;
  outline-offset: 3px;
  transition: outline-color 0.22s var(--ease), box-shadow 0.22s var(--ease);
}

/* Some rows name a class of elements rather than one — "any row" of the
   history list, the three sidebar actions, the two about lines. Ringing each
   match drew a fence: seven outlines whose 3px offsets met in the 0px gaps
   between rows, doubling into a grid that hid the rows it meant to point at.
   A set gets one ring around the block the matches form instead, which is also
   what the row says. Sized in the phone's own logical pixels by app.js.

   13px = the rows' own 10px corner plus the 3px the ring stands off them, so
   the ring stays concentric with what it is drawn around. */
.spot-ring {
  position: absolute;
  pointer-events: none;
  border-radius: 13px;
}

.tour__hint {
  display: none;
  margin: 30px 0 10px;
  font-size: 12px;
  letter-spacing: 0.04em;
  color: var(--site-faint);
}

/* The whole link between row and screen is a hover affordance, so none of it is
   offered to devices that cannot hover. Browsers without :has simply get the
   list, which reads on its own. */
@media (hover: hover) {
  .tour__hint { display: block; }

  /* The row lights as a whole. A badge changing colour was too small a signal
     for "this line now points at that element". */
  .anno:hover::before,
  .anno:focus-visible::before { opacity: 1; }

  .anno:hover .anno__num,
  .anno:focus-visible .anno__num {
    color: var(--bg);
    background: var(--accent-deep);
    border-color: var(--accent-deep);
  }
  .anno:hover .anno__label,
  .anno:focus-visible .anno__label { color: var(--accent-deep); }

  /* The values are the specifics, so they come up to full strength when their
     row is the one being read. */
  .anno:hover .anno__value,
  .anno:focus-visible .anno__value { color: var(--text); }

  .tour__panel:has(.anno[data-ref="1"]:hover) [data-spot="1"],
  .tour__panel:has(.anno[data-ref="2"]:hover) [data-spot="2"],
  .tour__panel:has(.anno[data-ref="3"]:hover) [data-spot="3"],
  .tour__panel:has(.anno[data-ref="1"]:focus-visible) [data-spot="1"],
  .tour__panel:has(.anno[data-ref="2"]:focus-visible) [data-spot="2"],
  .tour__panel:has(.anno[data-ref="3"]:focus-visible) [data-spot="3"] {
    outline-color: var(--accent);
    box-shadow: 0 0 0 10px var(--accent-wash);
  }
}

/* Swapping screens is a change of subject, so it gets its own small entrance
   rather than borrowing the scroll reveal.

   The two panels cross-fade: the outgoing one is lifted out of flow and left
   sitting exactly where it was while it fades, so nothing ever blinks to
   nothing between screens. The device frame is at identical coordinates in
   every panel, so fading it with no transform of its own reads as the screen's
   contents dissolving inside a phone that never moves.

   Both fades run linear, and the page's own ease is deliberately not used for
   them. It is front-loaded, so under it the incoming panel is already at 86%
   while the outgoing has dropped to 9% — the two never sum to a full image and
   the swap dips pale in the middle. Linear against linear is a true dissolve,
   and since consecutive screens are near-identical pixels it is invisible. */
@keyframes tour-enter { from { opacity: 0; } to { opacity: 1; } }
@keyframes tour-leave { from { opacity: 1; } to { opacity: 0; } }

/* The phone cross-dissolves cleanly because consecutive screens are near
   identical, but two columns of black text at 50% are just legible enough to
   read as each other's ghost. So the text is not part of the dissolve: the old
   column clears in the first 160ms and the new one starts after that, leaving
   the middle of the swap with a dissolving phone beside an empty column. */
@keyframes tour-detail-out { to { opacity: 0; } }
@keyframes tour-detail-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

.tour__panel.is-live { animation: tour-enter 0.32s linear both; }
.tour__panel.is-live .tour__detail {
  animation: tour-detail-in 0.42s var(--ease) 0.14s both;
}
.tour__panel.is-leaving .tour__detail {
  animation: tour-detail-out 0.16s linear both;
}

.tour__panel.is-leaving {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  pointer-events: none;
  animation: tour-leave 0.32s linear both;
}

/* Panels are not all the same height, so the stage eases between them instead
   of the page jumping under the pointer mid-click. */
.tour__stage.is-morphing { transition: height 0.42s var(--ease); }

/* --- swiping, on touch screens (app.js) -----------------------------------
   pan-y leaves vertical scrolling to the browser and hands the script only
   the horizontal gestures. While a finger is down the live panel tracks it
   directly, with no easing to lag behind the touch; on release it springs
   back, and when the swipe commits, the panels slide the way the finger went
   as well as dissolving — the same 32px in each direction, so the incoming
   screen arrives from where the outgoing one is heading. */
.tour__stage { touch-action: pan-y; }
.tour__panel { transition: transform 0.3s var(--ease); }
.tour__panel.is-dragging { transition: none; }
/* The entrance has played out; let go of it so the drag can move the panel
   (a filling animation would otherwise pin transform to none). */
.tour__panel.is-live.is-settled { animation: none; }

/* --drag-x is where the finger left the outgoing panel (set by app.js), so
   its slide continues from there instead of snapping to zero first. */
@keyframes tour-enter-next {
  from { opacity: 0; transform: translateX(32px); }
  to   { opacity: 1; transform: none; }
}
@keyframes tour-leave-next {
  from { opacity: 1; transform: translateX(var(--drag-x, 0px)); }
  to   { opacity: 0; transform: translateX(-32px); }
}
@keyframes tour-enter-prev {
  from { opacity: 0; transform: translateX(-32px); }
  to   { opacity: 1; transform: none; }
}
@keyframes tour-leave-prev {
  from { opacity: 1; transform: translateX(var(--drag-x, 0px)); }
  to   { opacity: 0; transform: translateX(32px); }
}
.tour__stage.swipe-next .tour__panel.is-live    { animation-name: tour-enter-next; }
.tour__stage.swipe-next .tour__panel.is-leaving { animation-name: tour-leave-next; }
.tour__stage.swipe-prev .tour__panel.is-live    { animation-name: tour-enter-prev; }
.tour__stage.swipe-prev .tour__panel.is-leaving { animation-name: tour-leave-prev; }

/* The pointing hint is for mice; a touch screen gets told about the swipe
   instead (the script rewrites the text). */
.tour__stage.is-swipable .tour__hint { display: block; }

/* ==========================================================================
   9. DEVICE FRAME + APP REPLICAS
   Everything below is a 1:1 port of a React Native component. Sizes are the
   app's own logical pixels; the whole phone is then scaled with a transform so
   the proportions survive. Source of truth: mobile/lib/theme.ts.
   ========================================================================== */

.phone-scaler {
  width: calc(414px * var(--phone-scale));
  height: calc(868px * var(--phone-scale));
  flex: none;
  /* The phone keeps a 414px layout box under its transform. Without this,
     flex and grid tracks size to that box instead of the painted width. */
  min-width: 0;
}

.phone {
  width: 414px;
  height: 868px;
  padding: 12px;
  border-radius: 56px;
  transform: scale(var(--phone-scale));
  transform-origin: top left;
  background: linear-gradient(152deg, #46464a 0%, #17171a 22%, #0c0c0e 55%, #202024 78%, #4a4a4f 100%);
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.1),
    0 2px 3px rgba(0, 0, 0, 0.5),
    0 34px 70px -24px rgba(0, 0, 0, 0.85);
}

.phone__screen {
  position: relative;
  width: 390px;
  height: 844px;
  border-radius: 44px;
  overflow: hidden;
  background: var(--bg-elevated);
  color: var(--text);
  display: flex;
  flex-direction: column;
  font-family: var(--font);
  line-height: 1.35;
}

.phone__island {
  position: absolute;
  top: 11px;
  left: 50%;
  translate: -50% 0;
  width: 118px;
  height: 35px;
  border-radius: var(--r-pill);
  background: #000;
  z-index: 20;
}

.phone__home {
  position: absolute;
  bottom: 8px;
  left: 50%;
  translate: -50% 0;
  width: 140px;
  height: 5px;
  border-radius: 3px;
  background: var(--text);
  opacity: 0.26;
  z-index: 20;
}

/* iOS status bar. Airplane mode is not set dressing — it is the claim the
   product makes, shown in every screenshot. */
.eg-statusbar {
  height: 54px;
  flex: none;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  padding: 0 30px 4px;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--text);
}
.eg-statusbar__right { display: flex; align-items: center; gap: 6px; }
.eg-statusbar svg { width: 16px; height: 16px; display: block; }

/* --- TopBar.tsx --- */
.eg-topbar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 16px;
  min-height: 56px;
  flex: none;
  border-bottom: 1px solid var(--border-soft);
}
.eg-menu-btn {
  width: 40px; height: 40px;
  border-radius: var(--r-md);
  background: var(--surface);
  display: grid; place-items: center;
  flex: none;
  color: var(--text);
}
.eg-menu-btn svg { width: 22px; height: 22px; }
.eg-status { display: flex; align-items: center; gap: 8px; }
.eg-dot { width: 8px; height: 8px; border-radius: 4px; flex: none; }
.eg-dot--ready { background: var(--ready); }
.eg-dot--idle { background: var(--idle); }
.eg-dot--loading { background: var(--loading); }
.eg-dot--generating { background: var(--accent); }
.eg-status__text { font-size: 14px; color: var(--text-muted); white-space: nowrap; }
.eg-spacer { flex: 1; }
.eg-private {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 14px;
  border-radius: var(--r-pill);
  border: 1px solid var(--border);
  color: var(--text-muted);
  flex: none;
}
.eg-private svg { width: 18px; height: 18px; }
.eg-private__label { font-size: 14px; white-space: nowrap; }
.eg-private.is-on { border-color: var(--private-tint); color: var(--private-tint); }

/* --- progress bar shown while the model downloads --- */
.eg-progress { height: 2px; background: var(--border-soft); flex: none; }
.eg-progress__fill { height: 2px; background: var(--accent); }

/* --- Welcome.tsx --- */
.eg-welcome {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 0 16px;
  margin-bottom: 80px;
}
.eg-welcome__title {
  font-size: 42px;
  line-height: 46px;
  letter-spacing: 2px;
  text-align: center;
  color: var(--text);
  margin: 0;
  font-weight: 400;
}
.eg-welcome__rule { width: 60px; height: 1px; background: var(--border); margin: 4px 0; }
.eg-welcome__sub {
  font-size: 12px;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0;
}
.eg-load-btn {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px;
  border-radius: var(--r-pill);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 14px;
  font-weight: 500;
}
.eg-load-btn svg { width: 18px; height: 18px; }
.eg-loading-text { font-size: 13px; color: var(--text-muted); }

/* --- Thread.tsx / MessageBubble.tsx --- */
.eg-thread {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  padding: 12px 0 92px;
  display: flex;
  flex-direction: column;
}
.eg-msg { padding: 12px 16px; }
.eg-q, .eg-a { display: flex; align-items: flex-start; gap: 12px; }
.eg-q__icon {
  width: 28px; height: 28px;
  border-radius: var(--r-md);
  background: var(--surface);
  display: grid; place-items: center;
  margin-top: 2px;
  flex: none;
  color: var(--text-muted);
}
.eg-q__icon svg { width: 16px; height: 16px; }
.eg-q__text {
  font-size: 18px;
  line-height: 26px;
  font-weight: 500;
  color: var(--text);
  margin: 0;
}
.eg-a__avatar { width: 28px; height: 28px; border-radius: 14px; margin-top: 2px; flex: none; }
.eg-a__text {
  font-size: 17px;
  line-height: 25px;
  color: var(--text);
  margin: 0;
  white-space: pre-line;
}
.eg-caret { color: var(--accent); }

/* --- input dock (app/index.tsx) --- */
.eg-dock {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  background: var(--bg-elevated);
  border-top: 1px solid var(--border-soft);
  padding: 8px 12px 30px;
  z-index: 10;
}
.eg-stop-wrap { display: flex; justify-content: center; margin-bottom: 8px; }
.eg-stop {
  display: flex; align-items: center; gap: 6px;
  padding: 6px 12px;
  border-radius: var(--r-pill);
  border: 1px solid var(--border);
  background: var(--surface);
  font-size: 12px;
  color: var(--text);
}
.eg-stop svg { width: 14px; height: 14px; }
.eg-inputbox {
  display: flex; align-items: flex-end; gap: 8px;
  border-radius: var(--r-xl);
  border: 1px solid var(--input-border);
  background: var(--surface);
  padding: 6px 12px;
}
.eg-inputbox.is-private { border-color: var(--private-tint); }
.eg-input {
  flex: 1;
  min-height: 38px;
  padding: 6px 0;
  font-size: 17px;
  line-height: 26px;
  color: var(--text-faint);
}
.eg-input.has-value { color: var(--text); }
.eg-send {
  width: 34px; height: 34px;
  border-radius: 17px;
  background: var(--surface-alt);
  color: var(--text-faint);
  display: grid; place-items: center;
  flex: none;
}
.eg-send svg { width: 18px; height: 18px; }
.eg-send.is-active { background: var(--text); color: var(--bg); }

/* --- Sidebar.tsx (drawer, full width on phones) --- */
.eg-sidebar {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 12px 16px 16px;
}
.eg-hamburger {
  width: 40px; height: 40px;
  border-radius: var(--r-md);
  background: var(--surface);
  display: grid; place-items: center;
  margin-bottom: 10px;
  color: var(--sidebar-text);
}
.eg-hamburger svg { width: 22px; height: 22px; }
.eg-sb-logo { width: 32px; height: 32px; border-radius: 15px; opacity: 0.9; }
.eg-sb-logo-row { padding: 0 4px; margin-bottom: 18px; }
.eg-iconbtn {
  display: flex; align-items: center; gap: 12px;
  padding: 8px 10px;
  border-radius: var(--r-md);
  margin-top: 2px;
  color: var(--sidebar-text);
}
.eg-iconbtn__icon {
  width: 26px; height: 26px;
  display: grid; place-items: center;
  opacity: 0.75;
  flex: none;
}
.eg-iconbtn__icon svg { width: 24px; height: 24px; }
.eg-iconbtn__label { font-size: 20px; font-weight: 500; }
.eg-iconbtn.is-active { background: var(--sidebar-bg-active); }
.eg-iconbtn.is-active .eg-iconbtn__icon { opacity: 1; }
.eg-iconbtn.is-active .eg-iconbtn__label { color: var(--sidebar-text-active); }
.eg-recent { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--border-soft); }
.eg-recent__label {
  font-size: 14px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 1px;
  padding: 0 10px 8px;
  color: var(--text-faint);
}
.eg-recent__item {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 10px;
  border-radius: var(--r-md);
  font-size: 17px;
  color: var(--sidebar-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.eg-recent__item.is-active { background: var(--sidebar-bg-active); color: var(--sidebar-text-active); }
.eg-sb-footer {
  display: flex; align-items: center; justify-content: space-between;
  border-top: 1px solid var(--border-soft);
  padding-top: 12px;
}

/* --- history.tsx --- */
.eg-hist-header { display: flex; align-items: center; gap: 12px; padding: 12px; }
.eg-searchbar {
  flex: 1;
  display: flex; align-items: center; gap: 8px;
  padding: 8px 12px;
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text-muted);
}
.eg-searchbar svg { width: 20px; height: 20px; flex: none; }
.eg-searchbar__input { font-size: 17px; color: var(--text-faint); }
.eg-close {
  width: 38px; height: 38px;
  border-radius: var(--r-md);
  background: var(--surface);
  display: grid; place-items: center;
  flex: none;
  color: var(--text);
}
.eg-close svg { width: 18px; height: 18px; }
.eg-filters { display: flex; gap: 6px; padding: 0 12px 10px; }
.eg-filter {
  padding: 4px 10px;
  border-radius: 8px;
  border: 1px solid var(--pill-border);
  font-size: 15px; font-weight: 500;
  color: var(--text-muted);
}
.eg-filter.is-active {
  background: var(--pill-active-bg);
  border-color: var(--pill-active-border);
  color: var(--text);
}
.eg-hrow {
  display: flex; align-items: center; gap: 12px;
  margin: 0 8px;
  padding: 12px;
  border-radius: var(--r-md);
}
.eg-hrow__title {
  font-size: 17px; color: var(--sidebar-text);
  margin-bottom: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.eg-hrow__date { font-size: 14px; color: var(--text-faint); }
.eg-hrow__del { padding: 8px; color: var(--text-muted); display: grid; place-items: center; }
.eg-hrow__del svg { width: 16px; height: 16px; }

/* --- settings.tsx --- */
.eg-set-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px;
  border-bottom: 1px solid var(--border-soft);
  flex: none;
}
.eg-set-header__title { font-size: 20px; font-weight: 600; color: var(--text); }
.eg-back {
  width: 38px; height: 38px;
  border-radius: var(--r-md);
  background: var(--surface);
  display: grid; place-items: center;
  color: var(--text);
}
.eg-back svg { width: 20px; height: 20px; }
.eg-back--ghost { background: none; }
.eg-section-block { padding: 16px; display: flex; flex-direction: column; gap: 12px; }
.eg-section-block__title {
  font-size: 14px; font-weight: 500;
  text-transform: uppercase; letter-spacing: 0.5px;
  color: var(--text-muted);
}
.eg-toggle-row, .eg-about-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.eg-toggle-row__text { flex: 1; display: flex; flex-direction: column; gap: 2px; }
.eg-row-label { font-size: 15px; font-weight: 500; color: var(--text); }
.eg-row-desc { font-size: 13px; line-height: 18px; color: var(--text-muted); }
.eg-about-row__value { font-size: 14px; color: var(--text-muted); text-align: right; }
.eg-danger {
  padding: 8px 16px;
  border-radius: var(--r-pill);
  border: 1px solid var(--error);
  color: var(--error);
  font-size: 14px; font-weight: 500;
  flex: none;
}
/* iOS Switch, as rendered by RN's <Switch> with the app's trackColor */
.eg-switch {
  width: 51px; height: 31px;
  border-radius: var(--r-pill);
  background: var(--surface-alt);
  position: relative;
  flex: none;
}
.eg-switch::after {
  content: "";
  position: absolute;
  top: 2px; left: 2px;
  width: 27px; height: 27px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
}
.eg-switch.is-on { background: var(--accent); }
.eg-switch.is-on::after { left: 22px; }

/* --------------------------------------------------------------------------
   10. Answer card — the site explains itself in the app's output format
   (numbered steps, one short sentence each, max eight — see SYSTEM_PROMPT).
   -------------------------------------------------------------------------- */

.answer {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  max-width: 720px;
}
.answer__avatar {
  width: 34px; height: 34px;
  border-radius: 17px;
  flex: none;
  margin-top: 3px;
}
.answer__steps {
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: step;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.answer__steps li {
  counter-increment: step;
  display: flex;
  gap: 12px;
  font-size: 17px;
  line-height: 1.62;
  color: var(--text);
}
.answer__steps li::before {
  content: counter(step) ".";
  font-family: var(--font-mono);
  font-size: 15px;
  color: var(--site-faint);
  flex: none;
  min-width: 20px;
  padding-top: 2px;
}
.answer__steps em {
  font-style: normal;
  color: var(--text-muted);
}

/* The refusal, quoted verbatim from the system prompt and set in the app's
   own answer type. It is the clearest statement of what this thing is. */
.refusal { margin-top: 40px; }
.refusal__text {
  font-size: 17px;
  line-height: 1.62;
  color: var(--text);
  margin: 0;
  max-width: 60ch;
}
.refusal__note {
  margin: 12px 0 0;
  font-size: 13px;
  color: var(--site-faint);
  font-family: var(--font-mono);
}

/* --------------------------------------------------------------------------
   11. Privacy diagram
   -------------------------------------------------------------------------- */

.diagram {
  width: 100%;
  max-width: 860px;
  height: auto;
}
/* The wide diagram squeezed into a phone renders its 14px labels at about 5px.
   Same mechanism, stacked, for narrow screens. */
.diagram--tall { display: none; max-width: 340px; }
@media (max-width: 700px) {
  .diagram--wide { display: none; }
  .diagram--tall { display: block; margin-inline: auto; }
}
.dg-boundary { fill: none; stroke: var(--accent); stroke-width: 1.5; stroke-dasharray: 7 6; opacity: 0.7; }
.dg-node { fill: var(--surface); stroke: var(--border); stroke-width: 1; }
.dg-node--dead { fill: none; stroke: var(--border); stroke-dasharray: 4 5; }
.dg-line { stroke: var(--text-faint); stroke-width: 1.4; fill: none; }
.dg-line--dead { stroke: var(--text-faint); stroke-width: 1.4; opacity: 0.4; stroke-dasharray: 4 5; }
.dg-arrow { fill: var(--text-faint); }
.dg-text { fill: var(--text); font-size: 14px; font-family: var(--font); }
.dg-text--muted { fill: var(--text-muted); font-size: 13px; }
.dg-label {
  fill: var(--accent-deep);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.18em;
  font-family: var(--font);
}
.dg-label--off { fill: var(--site-faint); }
.dg-cut { stroke: var(--error); stroke-width: 2; stroke-linecap: round; }

.claims {
  display: grid;
  gap: 0;
  margin-top: 52px;
  max-width: 820px;
  border-top: 1px solid var(--border);
}
.claim {
  display: grid;
  gap: 4px 24px;
  padding: 20px 0;
  border-bottom: 1px solid var(--border);
}
.claim__title { font-size: 16px; font-weight: 500; color: var(--text); margin: 0; }
.claim__body { font-size: 15px; line-height: 1.6; color: var(--text-muted); margin: 0; }
@media (min-width: 760px) {
  .claim { grid-template-columns: 210px 1fr; align-items: baseline; }
}

/* --------------------------------------------------------------------------
   12. Scope chips + specs
   -------------------------------------------------------------------------- */

.chips { display: flex; flex-wrap: wrap; gap: 8px; max-width: 840px; }
.chip {
  padding: 7px 13px;
  border-radius: var(--r-pill);
  border: 1px solid var(--border);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-muted);
  transition: color 0.16s ease, border-color 0.16s ease;
}
.chip:hover { color: var(--text); border-color: var(--pill-active-border); }

.specs {
  display: grid;
  gap: 0 clamp(32px, 5vw, 72px);
  max-width: 900px;
}
@media (min-width: 800px) { .specs { grid-template-columns: 1fr 1fr; } }

/* Settings → About row rhythm: label left, muted value right, hairline under. */
.spec {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 20px;
  padding: 15px 0;
  border-bottom: 1px solid var(--border);
}
.spec__label { font-size: 15px; font-weight: 500; color: var(--text); }
.spec__value {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--text-muted);
  text-align: right;
  word-break: break-word;
}

/* --------------------------------------------------------------------------
   13. Closing + footer
   -------------------------------------------------------------------------- */

.outro {
  position: relative;
  isolation: isolate;
  text-align: center;
  padding-block: clamp(84px, 13vh, 150px);
  border-top: 1px solid var(--border);
  overflow: hidden;
}
.outro__inner { display: flex; flex-direction: column; align-items: center; }
.outro__mark { width: 76px; height: 76px; border-radius: 38px; opacity: 0.92; }
.outro__title {
  margin: 26px 0 0;
  font-size: clamp(30px, 5vw, 50px);
  font-weight: 300;
  letter-spacing: 0.055em;
  line-height: 1.06;
}
.outro .rule { margin-inline: auto; }
.outro__actions { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; margin-top: 30px; }
.outro__fine {
  margin: 24px 0 0;
  font-size: 13px;
  color: var(--site-faint);
  font-family: var(--font-mono);
}

.site-footer { border-top: 1px solid var(--border); }
.site-footer__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px 28px;
  padding-block: 30px;
  font-size: 13px;
  color: var(--site-faint);
}
.site-footer__links { display: flex; gap: 20px; margin-left: auto; }
.site-footer a { color: var(--text-muted); text-decoration: none; }
.site-footer a:hover { color: var(--text); }

/* --------------------------------------------------------------------------
   14. Atmosphere
   The page is white paper with an engraving on it. Everything here adds depth
   to the paper without adding colour to it: four-point stars lifted out of the
   canvas so they can breathe and drift. The paper itself stays the app's own
   lightColors.bgElevated, edge to edge — there is no wash, glow or tint behind
   any section, so the site's background is exactly the app's.
   -------------------------------------------------------------------------- */

/* Four-point stars, the logo's own mark. They used to be painted into the
   stipple canvas, which meant they could not move; as elements they can
   twinkle on their own clocks and drift against the pointer. */
.sparks {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
}
.spark {
  position: absolute;
  width: var(--s);
  height: var(--s);
  margin: calc(var(--s) / -2) 0 0 calc(var(--s) / -2);
  background: var(--ink-alt);
  clip-path: polygon(
    50% 0%, 58% 42%, 100% 50%, 58% 58%,
    50% 100%, 42% 58%, 0% 50%, 42% 42%
  );
  opacity: 0;
  /* --px/--py are set on .sparks and inherit; --d is this star's depth, so a
     single pointer reading parallaxes the whole field by layers. */
  transform: translate3d(
    calc(var(--px, 0px) * var(--d)),
    calc(var(--py, 0px) * var(--d)),
    0
  );
  transition: transform 0.6s var(--ease);
  animation: spark-twinkle var(--dur) ease-in-out var(--delay) infinite;
}
.spark--cool { background: var(--ink-alt); }

@keyframes spark-twinkle {
  0%, 100% { opacity: var(--o0); }
  50%      { opacity: var(--o1); }
}

.rule--glint {
  background: linear-gradient(90deg, var(--accent-mid), transparent);
  opacity: 0.9;
}

/* --------------------------------------------------------------------------
   15. Reading progress
   -------------------------------------------------------------------------- */

/* How far down the page you are, along the bottom edge of the floating bar. */
.site-header__progress {
  position: absolute;
  left: 16px;
  right: 16px;
  bottom: 0;
  height: 1px;
  overflow: hidden;
  pointer-events: none;
}
.site-header__progress::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, var(--accent-mid) 32%, var(--accent-soft));
  transform: scaleX(var(--sp, 0));
  transform-origin: left;
}

/* --------------------------------------------------------------------------
   16. Hover and entrance
   -------------------------------------------------------------------------- */

/* A glint crosses the primary button, same light as the wordmark's. */
.btn--primary {
  position: relative;
  overflow: hidden;
}
.btn--primary::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(
    104deg,
    transparent 36%,
    rgba(255, 255, 255, 0.22) 50%,
    transparent 64%
  );
  transform: translateX(-130%);
  transition: transform 0.7s var(--ease);
}
.btn--primary:hover::after { transform: translateX(130%); }

.btn--ghost:hover,
.btn:not(.btn--primary):hover {
  border-color: color-mix(in srgb, var(--accent-mid) 50%, transparent);
}

.chip {
  transition: color 0.16s ease, border-color 0.16s ease,
              box-shadow 0.22s ease, transform 0.22s var(--ease);
}
.chip:hover {
  color: var(--text);
  border-color: color-mix(in srgb, var(--accent-mid) 70%, transparent);
  box-shadow: 0 0 0 3px var(--accent-wash);
  transform: translateY(-1px);
}

.hero__device { position: relative; }
.hero__device .phone-scaler { animation: phone-float 11s ease-in-out infinite alternate; }

@keyframes phone-float {
  from { transform: translateY(-5px); }
  to   { transform: translateY(7px); }
}

/* Groups populate after their block arrives, in the order you would read them. */
.js [data-stagger] > * {
  opacity: 0;
  transform: translateY(9px);
  transition: opacity 0.55s var(--ease), transform 0.55s var(--ease);
  transition-delay: calc(120ms + var(--i, 0) * 42ms);
}
[data-stagger].is-lit > * {
  opacity: 1;
  transform: none;
}

/* --------------------------------------------------------------------------
   17. The privacy circuit
   The diagram was a still picture of a claim that is about movement. Two
   packets now run the on-device loop — question, model, answer, storage, back
   — and never cross the dashed boundary. The cut is struck when you arrive.
   -------------------------------------------------------------------------- */

.dg-boundary { animation: dg-march 4s linear infinite; }
@keyframes dg-march { to { stroke-dashoffset: -26; } }

.dg-packet {
  fill: var(--accent-mid);
  filter: drop-shadow(0 0 4px var(--accent-glow));
}
.dg-packet--wide {
  offset-path: path("M115,128 H512 V216 H115 Z");
  animation: dg-run 11s linear infinite;
}
.dg-packet--wide.dg-packet--b { animation-delay: -5.5s; }
.dg-packet--tall {
  offset-path: path("M170,62 V260");
  animation: dg-run-fade 5.6s linear infinite;
}
.dg-packet--tall.dg-packet--b { animation-delay: -2.8s; }

@keyframes dg-run { from { offset-distance: 0%; } to { offset-distance: 100%; } }
@keyframes dg-run-fade {
  0%        { offset-distance: 0%;   opacity: 0; }
  14%, 86%  { opacity: 1; }
  100%      { offset-distance: 100%; opacity: 0; }
}

/* Armed by script, so the cut is simply drawn if script never runs. */
.js-diagram.is-armed .dg-cut { stroke-dasharray: 34; stroke-dashoffset: 34; }
.js-diagram.is-cut .dg-cut { animation: dg-strike 0.36s var(--ease) forwards; }
.js-diagram.is-cut .dg-cut:last-of-type { animation-delay: 0.12s; }
@keyframes dg-strike { to { stroke-dashoffset: 0; } }

/* --------------------------------------------------------------------------
   18. Motion
   One orchestrated moment (the hero types a real answer). Everything else is
   a single quiet reveal.
   -------------------------------------------------------------------------- */

@keyframes eg-blink { 0%, 45% { opacity: 1; } 55%, 100% { opacity: 0; } }
.eg-caret { animation: eg-blink 1s steps(1) infinite; }

.js .reveal {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.75s cubic-bezier(0.2, 0.7, 0.3, 1),
              transform 0.75s cubic-bezier(0.2, 0.7, 0.3, 1);
}
.reveal.is-in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .js .reveal { opacity: 1; transform: none; transition: none; }
  .eg-caret { animation: none; }

  /* Anything that only exists to move is removed rather than frozen: a stalled
     packet reads as a bug, and a halted sweep sits as a pale band over the
     wordmark. What stays is the still version of the same picture. */
  .dg-packet,
  .js [data-stagger] > * { opacity: 1; transform: none; }
  .spark { opacity: var(--o1); }
  .hero__device .phone-scaler { transform: none; }

  * { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
}

/* --------------------------------------------------------------------------
   19. Responsive
   -------------------------------------------------------------------------- */

@media (min-width: 1000px) { .hero__device { --phone-scale: 0.86; } }
@media (min-width: 701px) and (max-width: 999px) { .hero__device { --phone-scale: 0.78; } }

@media (max-width: 640px) {
  .hero__facts { display: grid; grid-template-columns: repeat(3, 1fr); max-width: none; }
  .fact { margin-right: 0; padding: 14px 10px 4px 0; }
  .fact__value { font-size: 17px; }
  .fact__label { font-size: 10px; letter-spacing: 0.12em; }
}

/* --- phones ---------------------------------------------------------------
   A device mockup is height-dominant, so the scale is chosen to leave a whole
   screen plus its explanation inside the viewport, and tightened again on short
   phones where height, not width, is the binding constraint. */
@media (max-width: 700px) {
  html { scroll-padding-top: 82px; }

  :root {
    /* 54px bar on phones, not 60. */
    --bar-h: 66px;
    --phone-scale: 0.72;
    --section-pad: 52px;
    --gutter: 20px;
  }

  .site-header__bar { height: 54px; padding-inline: 14px; border-radius: 15px; }

  .h-display { font-size: clamp(38px, 11vw, 96px); }
  .q-row__text { font-size: clamp(21px, 6vw, 34px); }
  .lede { font-size: 16px; }
  .outro__title { font-size: clamp(26px, 8vw, 50px); }

  .section__sub { padding-left: 0; margin-top: 16px; font-size: 15px; }
  .q-row { gap: 12px; }
  .q-row__icon { width: 34px; height: 34px; border-radius: var(--r-md); }
  .q-row__icon svg { width: 16px; height: 16px; }

  .hero { padding-top: calc(32px + var(--bar-h)); padding-bottom: 40px; }
  .hero__title { margin-top: 14px; }
  .hero__motto { margin-bottom: 20px; }
  .hero__lede { margin-bottom: 24px; }
  .hero__actions { margin-bottom: 26px; }

  /* Showing a whole phone on a phone spends a full screen restating something
     the reader is already holding. Crop it: the streaming answer is the part
     that matters, and the fade says the screen continues. */
  .hero__device {
    --phone-scale: 0.76;
    height: 404px;
    overflow: hidden;
    -webkit-mask-image: linear-gradient(#000 72%, transparent 99%);
    mask-image: linear-gradient(#000 72%, transparent 99%);
  }
  /* The crop is a fixed height, so a floating phone would pump against the
     mask's fade instead of hovering. It sits still here. */
  .hero__device .phone-scaler { animation: none; }

  /* Two CTAs stack into two rows at default size; trimming the padding and the
     label fits them on one. */
  .btn { padding: 11px 15px; font-size: 14px; gap: 8px; }
  .site-header__cta { padding: 8px 14px; font-size: 13px; }

  /* Seven pills wrap to three rows at this width. One row that scrolls costs
     90px less. Bleeds to both screen edges so it reads as scrollable. */
  .tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: calc(var(--gutter) - var(--tab-pad)) var(--gutter);
    scroll-padding-inline: var(--gutter);
  }
  .tabs::-webkit-scrollbar { display: none; }
  .tab { flex: none; }

  .chips { gap: 6px; }
  .chip { padding: 5px 10px; font-size: 11px; letter-spacing: 0.06em; }

  .section__body { margin-top: 28px; }
  .tour__stage { margin-top: 24px; }
  .tour__desc { font-size: 17px; max-width: none; }
  .tour__anno { margin-top: 22px; }
  /* Label and value share a row comfortably at 320px, but the value is the
     shorter half — let it wrap under rather than squeeze the label. */
  .anno { grid-template-columns: 21px minmax(0, 1fr); gap: 2px 12px; padding: 11px 0; }
  .anno__num { grid-row: span 2; align-self: start; margin-top: 1px; }
  .anno__value { text-align: left; }
  .claims { margin-top: 32px; }
  .claim { padding: 16px 0; }
  .spec {
    flex-direction: column;
    align-items: flex-start;
    gap: 3px;
    padding: 10px 0;
  }
  .spec__label { font-size: 14px; }
  .spec__value { text-align: left; }
  .answer__steps li { font-size: 16px; }
  .refusal { margin-top: 30px; }
  .outro { padding-block: 60px; }
  .outro__mark { width: 62px; height: 62px; }
}

/* Narrow phones: keep the mockup clear of the gutters, and let the header's
   button give ground so the wordmark never has to. These sit after the
   max-width:700 block deliberately — same specificity, so source order decides,
   and the earlier placement lost the bar's padding back to it. */
@media (max-width: 400px) {
  :root { --phone-scale: 0.68; }
  .hero__device { --phone-scale: 0.72; }

  .site-header__cta-label { display: none; }
  .site-header__cta { padding: 8px 11px; }
}

@media (max-width: 360px) {
  .brand__name { font-size: 13px; }
  /* 180 + 45 leaves room, but the bar's 16px flex gap does not: it is the gap
     that pushes the glyph past the edge here, not the two items. */
  .site-header__bar { padding-inline: 11px; gap: 8px; }
}

/* Short viewports last — height binds harder than width for a device mockup,
   and this has to win over the rule above. */
@media (max-width: 700px) and (max-height: 700px) {
  :root { --phone-scale: 0.58; }
  .hero__device { --phone-scale: 0.64; height: 312px; }
}
