/* Quick Balance site.
   Colours are the app's own tokens, from
   app/src/main/java/gy/keoma/quickbalance/ui/theme/Tokens.kt. The site follows the
   visitor's system theme the way the app follows the phone's. */

:root {
  --bg: #f6f7f8;
  --surface: #ffffff;
  --sunken: #eceef1;
  --border: #e5e7eb;
  --text: #14171a;
  --muted: #5f6368;
  --grad-a: #2563eb;
  --grad-b: #7c3aed;
  --accent: #2563eb;
  --danger: #b42318;
  --success: #15803d;
  --on-accent: #ffffff;

  /* The light coming off the brand gradient, used for the glow behind the phone
     and under the primary button. */
  --bloom: color-mix(in oklab, var(--grad-a) 55%, var(--grad-b));

  /* The two pinned bars. Content is kept clear of the bottom one by hand, since a
     fixed element takes no space of its own. */
  --footbar: 3.1rem;

  --measure: 34rem;
  --gap: clamp(3.5rem, 9vw, 7rem);
  --radius: 18px;
}

/* Dark mode follows the device unless the header's sun/moon switch has chosen one; the
   choice lands as data-theme on <html>. Each dark rule is written twice - once for the
   device, once for the switch - because a media query cannot share a rule with a selector. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #000000;
    --surface: #141414;
    --sunken: #0b0b0b;
    --border: #2a2a2a;
    --text: #ffffff;
    --muted: #9ba1a6;
    --grad-a: #3b82f6;
    --grad-b: #8b5cf6;
    --accent: #3b82f6;
    --danger: #ef4444;
    --success: #22c55e;
  }
}

:root[data-theme="dark"] {
  --bg: #000000;
  --surface: #141414;
  --sunken: #0b0b0b;
  --border: #2a2a2a;
  --text: #ffffff;
  --muted: #9ba1a6;
  --grad-a: #3b82f6;
  --grad-b: #8b5cf6;
  --accent: #3b82f6;
  --danger: #ef4444;
  --success: #22c55e;
}

*, *::before, *::after { box-sizing: border-box; }

/* The header is pinned, so an anchor jump would otherwise land underneath it. */
html { -webkit-text-size-adjust: 100%; scroll-padding-top: 5.5rem; }

body {
  margin: 0;
  /* Clears the pinned bar, plus a little air so the page does not end flush against it. */
  padding-bottom: calc(var(--footbar) + 1.25rem + env(safe-area-inset-bottom, 0px));
  background: var(--bg);
  color: var(--text);
  font-family: Archivo, "Segoe UI", system-ui, -apple-system, sans-serif;
  font-size: 17px;
  line-height: 1.6;
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; height: auto; display: block; }

a { color: var(--accent); text-underline-offset: 3px; }
a:hover { text-decoration-thickness: 2px; }

:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

/* ---- type -------------------------------------------------------------- */

h1, h2, h3 {
  font-stretch: 112%;
  font-weight: 700;
  letter-spacing: -0.022em;
  line-height: 1.04;
  margin: 0;
  text-wrap: balance;
}

h1 { font-size: clamp(2.4rem, 6.4vw, 4rem); }
h2 { font-size: clamp(1.85rem, 4.2vw, 2.7rem); }
h3 { font-size: 1.18rem; line-height: 1.25; font-stretch: 106%; }

p { margin: 0 0 1rem; max-width: var(--measure); }
p:last-child { margin-bottom: 0; }

.lead {
  font-size: clamp(1.06rem, 2.1vw, 1.28rem);
  color: var(--muted);
  line-height: 1.55;
}

.ussd-code {
  font-family: inherit;
  font-stretch: 100%;
  font-weight: 600;
  white-space: nowrap;
}

/* ---- shell ------------------------------------------------------------- */

.wrap {
  width: min(68rem, 100% - 2.5rem);
  margin-inline: auto;
}

/* Pinned, so the menu is reachable from anywhere on a long page. Sticky rather than
   fixed: it keeps its own space in the flow, so nothing needs padding to clear it. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  border-bottom: 1px solid var(--border);
  background: color-mix(in oklab, var(--surface) 70%, var(--bg));
}

.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-block: 0.9rem;
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-stretch: 112%;
  font-weight: 700;
  font-size: 1.05rem;
  letter-spacing: -0.02em;
  color: var(--text);
  text-decoration: none;
}

/* The app's own launcher icon, from ../quick-balance via tools/icon.py. It used to be a
   gradient square with a $ drawn in it — the same picture, faked in CSS. The file is
   square and full-bleed, so the circle is cut here rather than baked in; only the
   favicon, which no stylesheet can reach, carries its own. */
.mark {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  flex: none;
}

.site-nav {
  display: flex;
  align-items: center;
  gap: 1.4rem;
  font-size: 0.95rem;
}

.site-nav a { color: var(--muted); text-decoration: none; }
.site-nav a:hover { color: var(--text); text-decoration: underline; }

/* The page you are on is named, not just coloured. */
.site-nav a[aria-current="page"] {
  color: var(--text);
  font-weight: 700;
}

.site-nav .nav-cta {
  color: #ffffff;
  padding: 0.55rem 1.15rem;
  font-size: 0.92rem;
}

.site-nav .store:hover { text-decoration: none; }

/* ---- store badges in the nav ------------------------------------------- */

/* Both are the vendors' own artwork and neither may be restyled, so the layout works
   around them instead. Google bakes 41px of mandated clear space into a 646x250 image and
   Apple's SVG carries none - but Google's inset is symmetric (measured: 564x168 artwork at
   offset 41,41), so simply centring the two lines the pills up. */
.nav-stores {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
}

/* 48px image / 1.4881 = a 32px pill, near the height of the button it replaced. */
.nav-stores .store { display: block; }
.nav-stores .store-play { display: block; width: auto; height: 48px; }
.nav-stores .store-apple { width: auto; height: 32px; }

/* Apple ships a black badge and a white one; the dark theme takes the white. */
.nav-stores .store-apple-black { display: block; }
.nav-stores .store-apple-white { display: none; }

/* There is no iOS build, so the badge is shown but inert - not a link, not focusable, and
   dimmed enough that it never reads as the thing to press. */
.store.is-soon {
  opacity: 0.4;
  cursor: default;
  pointer-events: none;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .nav-stores .store-apple-black { display: none; }
  :root:not([data-theme="light"]) .nav-stores .store-apple-white { display: block; }
}

:root[data-theme="dark"] .nav-stores .store-apple-black { display: none; }
:root[data-theme="dark"] .nav-stores .store-apple-white { display: block; }

/* Collapsed menu button. Hidden until the script confirms it can drive it, so with no
   JavaScript the links simply wrap and everything stays reachable. */
.nav-toggle {
  display: none;
  align-items: center;
  gap: 0.55rem;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.5rem 1rem;
  font: inherit;
  font-size: 0.92rem;
  font-weight: 700;
  cursor: pointer;
}

.nav-toggle-bars,
.nav-toggle-bars::before,
.nav-toggle-bars::after {
  display: block;
  width: 16px;
  height: 2px;
  background: currentColor;
  border-radius: 2px;
}

.nav-toggle-bars { position: relative; }
.nav-toggle-bars::before,
.nav-toggle-bars::after { content: ""; position: absolute; left: 0; }
.nav-toggle-bars::before { top: -5px; }
.nav-toggle-bars::after { top: 5px; }

/* ---- sun/moon switch --------------------------------------------------- */

/* A solid track with a solid knob. The knob sits over the mode in use and carries its
   icon; the other mode's icon shows on the track. Hidden until the script that drives it
   has run - with no JavaScript the device's own setting decides, as it always did. */
.theme-switch { display: none; }

.has-js .theme-switch {
  display: inline-flex;
  position: relative;
  flex: none;
  align-items: center;
  justify-content: space-between;
  width: 58px;
  height: 30px;
  padding: 0 8px;
  border: 0;
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  cursor: pointer;
}

.theme-switch svg { display: block; width: 14px; height: 14px; }

.theme-switch-knob {
  position: absolute;
  top: 3px;
  left: 3px;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: #ffffff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
  transition: transform 0.2s ease;
}

.theme-switch-knob .icon-sun { color: #d97706; }
.theme-switch-knob .icon-moon { color: #6d28d9; display: none; }
.theme-switch[aria-checked="true"] .theme-switch-knob { transform: translateX(28px); }
.theme-switch[aria-checked="true"] .theme-switch-knob .icon-sun { display: none; }
.theme-switch[aria-checked="true"] .theme-switch-knob .icon-moon { display: block; }

.theme-switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

@media (prefers-reduced-motion: reduce) {
  .theme-switch-knob { transition: none; }
}

/* Wide screens: the switch ends the row, after the store badges. Narrow ones: it stays out
   of the collapsed menu, beside the Menu button. */
@media (min-width: 64rem) {
  .site-nav { margin-left: auto; }
  .has-js .theme-switch { order: 2; }
}

@media (max-width: 63.99rem) {
  .site-header .wrap { flex-wrap: wrap; }
  .has-js .theme-switch { margin-left: auto; }
  .site-nav { flex-wrap: wrap; gap: 0.9rem 1.2rem; width: 100%; padding-bottom: 0.4rem; }
  .has-js .nav-toggle { display: inline-flex; }
  .has-js .site-nav { display: none; }
  .has-js .site-header.nav-open .site-nav {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 1rem;
    padding-block: 1rem 0.6rem;
    border-top: 1px solid var(--border);
    margin-top: 0.9rem;
    /* The header no longer scrolls away, so an open menu on a short screen has to
       scroll inside itself instead of running off the bottom. */
    max-height: calc(100dvh - 8rem);
    overflow-y: auto;
  }
  .site-nav .nav-cta { align-self: flex-start; }
}

section { padding-block: var(--gap); }

/* An inner page opens with a title and a line of context; it does not need a full
   section's worth of air beneath it before the content starts. */
main:not(.doc) > section:first-child { padding-bottom: calc(var(--gap) * 0.5); }

/* Headings sit tight against their own margin box, so the first paragraph after one
   needs the air put back. The document pages set their own heading margins. */
section h1 + p,
section h2 + p,
.fact h2 + div { margin-top: 1.1rem; }

/* Inner pages carry an h1, but only the hero earns the full display size. */
section h1 { font-size: clamp(2.1rem, 5vw, 3.2rem); }
.hero h1 { font-size: clamp(2.4rem, 6.4vw, 4rem); }

.section-link { margin-top: 2.2rem; }

/* ---- buttons ----------------------------------------------------------- */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.85rem 1.7rem;
  border-radius: 999px;
  font-weight: 700;
  font-size: 1rem;
  font-stretch: 106%;
  text-decoration: none;
  border: 1px solid transparent;
}

.button-primary {
  background: linear-gradient(90deg, var(--grad-a), var(--grad-b));
  color: #ffffff;
}

.button-primary {
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18),
              0 10px 22px -12px color-mix(in oklab, var(--bloom) 80%, transparent);
}

.button,
.site-nav a,
.site-footer nav a {
  transition: color 140ms ease, background 140ms ease,
              filter 140ms ease, transform 140ms ease, opacity 140ms ease;
}

.button-primary:hover { filter: brightness(1.06); transform: translateY(-1px); }
.button-primary:active { transform: translateY(0); filter: brightness(0.98); }

@media (prefers-reduced-motion: reduce) {
  .button-primary:hover { transform: none; }
}

/* ---- hero -------------------------------------------------------------- */

/* The hero sits on a soft ground with the brand's own light behind the phone, rather
   than on flat grey. */
.hero {
  padding-top: clamp(3rem, 7vw, 5.5rem);
  position: relative;
  isolation: isolate;
  background:
    radial-gradient(120% 80% at 78% 28%,
      color-mix(in oklab, var(--bloom) 18%, transparent) 0%,
      transparent 62%),
    linear-gradient(var(--surface), var(--bg) 70%);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .hero {
    background:
      radial-gradient(120% 80% at 78% 28%,
        color-mix(in oklab, var(--bloom) 26%, transparent) 0%,
        transparent 62%),
      linear-gradient(#0d0d0d, var(--bg) 70%);
  }
}

:root[data-theme="dark"] .hero {
  background:
    radial-gradient(120% 80% at 78% 28%,
      color-mix(in oklab, var(--bloom) 26%, transparent) 0%,
      transparent 62%),
    linear-gradient(#0d0d0d, var(--bg) 70%);
}

.hero .wrap {
  display: grid;
  gap: clamp(2.5rem, 6vw, 4.5rem);
  align-items: center;
}

@media (min-width: 60rem) {
  .hero .wrap { grid-template-columns: 1.05fr 0.95fr; }
}

/* ---- the home page, held to one screen --------------------------------- */

/* The home page does not scroll: the hero takes whatever is left between the two pinned
   bars and centres itself in it. body is border-box, so its own padding-bottom — the gap
   kept clear of the fixed footer — comes out of the 100dvh rather than adding to it.
   Everything that used to follow the hero has been taken off this page. */
.home {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

.home main { flex: 1; display: flex; }

/* The selector has to out-specify `main:not(.doc) > section:first-child`, which otherwise
   puts half a section gap under the hero and pushes the page into a scroll. */
.home main:not(.doc) > .hero:first-child {
  flex: 1;
  display: flex;
  align-items: center;
  /* Little above, less below: the block sits high on the screen instead of floating in the
     middle of it, and the room this frees goes to the phone rather than to air. */
  padding-block: clamp(0.5rem, 1.5vh, 1rem) clamp(0.25rem, 0.8vh, 0.6rem);
}

/* The generous gap is meant to separate the two columns. As a row gap it just pushes the
   buttons away from what they belong to, and off the bottom of the screen. */
.home .hero > .wrap { row-gap: clamp(0.6rem, 1.6vh, 1rem); }

/* Air around the phone that nothing depends on — the stage does not clip, it only sets the
   perspective — so on this page it goes to the phone itself instead. */
.home .qb-stage { padding: 0; }
.home .qb-hint { margin-top: 0.35rem; }

/* Full size on any screen with the room for it. The calc is a floor, not a shrink: it only
   bites once the window is too short to hold the phone at its normal size, and it trims the
   phone alone — the headline and the paragraph are never scaled down. 285px is the fixed
   furniture around it: both pinned bars, the hero padding and the hint line under it. */
.home .qb-phone { height: min(672px, 68vh, calc(100dvh - 225px)); }

/* Under 960px the columns stack, and stacked they cannot fit one screen — so the phone
   steps aside and the page is the headline, the paragraph and the buttons. */
@media (max-width: 59.99rem) {
  /* The .is-live form is needed too: once the demo mounts it sets display:grid from a
     rule further down this file, which would otherwise win on order. */
  .home .hero-demo,
  .home .hero-demo.is-live { display: none; }

  /* With the phone gone there is no right-hand side to hide behind, so the ribbon drops
     into the empty space under the copy and fades out before it reaches any of it. */
  .home .hero > .wave {
    top: auto;
    bottom: 0;
    height: clamp(160px, 30vh, 260px);
    transform: none;
  }

  .home .hero > .wave canvas {
    -webkit-mask-image:
      linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent),
      linear-gradient(180deg, transparent, #000 46%);
    mask-image:
      linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent),
      linear-gradient(180deg, transparent, #000 46%);
    -webkit-mask-composite: source-in;
    mask-composite: intersect;
  }
}

.hero-copy p { max-width: 32rem; }

.hero h1 {
  margin-bottom: 1.2rem;
  font-stretch: 118%;
  letter-spacing: -0.03em;
}

.hero .lead { color: var(--text); opacity: 0.78; }


/* Marked with a low underline rather than a padded chip: a chip opens a gap before the
   comma that follows it, which reads as a typo. */
.hero .ussd-code {
  font-stretch: 92%;
  font-weight: 700;
  color: color-mix(in oklab, var(--accent) 88%, var(--text));
  box-shadow: inset 0 -0.34em 0 color-mix(in oklab, var(--bloom) 15%, transparent);
}

/* ---- phone ------------------------------------------------------------- */

.phone {
  position: relative;
  width: min(330px, 80vw);
  margin-inline: auto;
  border: 11px solid #1c1c1e;
  border-radius: 44px;
  background: #000;
  box-shadow:
    0 2px 6px rgba(0, 0, 0, 0.16),
    0 14px 28px -12px rgba(0, 0, 0, 0.34),
    0 48px 80px -36px color-mix(in oklab, var(--bloom) 60%, rgba(0, 0, 0, 0.8));
  overflow: hidden;
  isolation: isolate;
}

/* The screen settles once the popup has gone: the answer is simply there, which is the
   whole argument this page is making. */
.phone img {
  border-radius: 33px;
  animation: settle 900ms cubic-bezier(0.2, 0.7, 0.3, 1) 3.9s both;
}

@keyframes settle {
  from { transform: scale(1.015); filter: brightness(0.82); }
  to   { transform: none;         filter: none; }
}

@media (prefers-reduced-motion: reduce) {
  .phone img { animation: none; }
}

/* The re-enactment: the phone's own USSD dialog, which is what this app exists to
   replace. It runs once on load and leaves the app's screen behind it. */
.ussd {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 1rem;
  background: rgba(0, 0, 0, 0.66);
  animation: ussd-clear 900ms ease 3.4s forwards;
}

.ussd-card {
  width: 100%;
  background: #303134;
  color: #e8eaed;
  border-radius: 16px;
  padding: 1.05rem 1.1rem 0.7rem;
  font-size: 0.8rem;
  line-height: 1.45;
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.6);
}

.ussd-card p { margin: 0 0 0.5rem; max-width: none; }
.ussd-card .dialled { font-weight: 700; font-size: 0.95rem; }
.ussd-card .menu { color: #b9bcc0; }

.ussd-timer {
  height: 2px;
  background: rgba(255, 255, 255, 0.16);
  margin: 0.7rem 0 0.35rem;
  overflow: hidden;
}

.ussd-timer i {
  display: block;
  height: 100%;
  width: 100%;
  background: #8ab4f8;
  transform-origin: left;
  animation: ussd-drain 3.2s linear forwards;
}

.ussd-actions {
  display: flex;
  justify-content: flex-end;
  gap: 1.1rem;
  color: #8ab4f8;
  font-weight: 700;
  font-size: 0.78rem;
  padding-block: 0.35rem;
}

@keyframes ussd-drain { to { transform: scaleX(0); } }
@keyframes ussd-clear { to { opacity: 0; visibility: hidden; } }

@media (prefers-reduced-motion: reduce) {
  .ussd { display: none; }
}

.phone-caption {
  margin: 1.1rem auto 0;
  max-width: 24rem;
  text-align: center;
  font-size: 0.88rem;
  color: var(--muted);
  line-height: 1.45;
}

/* ---- the three beats --------------------------------------------------- */

.beats {
  list-style: none;
  margin: 2.5rem 0 0;
  padding: 0;
  display: grid;
  gap: 0;
  counter-reset: beat;
}

@media (min-width: 46rem) {
  .beats { grid-template-columns: repeat(3, 1fr); gap: 2.2rem; }
}

.beats li {
  counter-increment: beat;
  padding-block: 1.4rem;
  border-top: 1px solid var(--border);
}

.beats li::before {
  content: counter(beat);
  display: block;
  font-stretch: 112%;
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--accent);
  margin-bottom: 0.5rem;
}

.beats h3 { margin-bottom: 0.35rem; }
.beats p { color: var(--muted); font-size: 0.95rem; max-width: none; }

/* ---- fact -------------------------------------------------------------- */

.fact {
  margin-top: var(--gap);
  padding: clamp(1.6rem, 4vw, 2.6rem);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.fact p { color: var(--muted); }
.fact .checked {
  margin-top: 1.2rem;
  font-size: 0.83rem;
  color: var(--muted);
}

@media (min-width: 54rem) {
  .fact {
    display: grid;
    grid-template-columns: 0.85fr 1.15fr;
    gap: 3rem;
    align-items: start;
  }
  .fact h2 + div { margin-top: 0; }
}

/* ---- widget ------------------------------------------------------------ */

.widget-shot {
  margin-top: 2.6rem;
  padding: clamp(1.4rem, 5vw, 3rem);
  background: var(--sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.widget-shot img {
  border-radius: 14px;
  width: min(560px, 100%);
  margin-inline: auto;
}

/* ---- features ---------------------------------------------------------- */

.split {
  display: grid;
  gap: clamp(2rem, 5vw, 3.5rem);
  align-items: center;
}

@media (min-width: 58rem) {
  .split { grid-template-columns: 1fr 1fr; }
  .split.reverse > :first-child { order: 2; }
}

.shot-frame {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  background: #000;
}

/* A full phone screen is far taller than it is wide; left at column width it swamps the
   text beside it. */
.shot-tall {
  width: min(300px, 100%);
  margin-inline: auto;
}

.feature-list {
  list-style: none;
  margin: 2.5rem 0 0;
  padding: 0;
}

.feature-list li {
  padding-block: 1.25rem;
  border-top: 1px solid var(--border);
  display: grid;
  gap: 0.3rem 2rem;
}

@media (min-width: 46rem) {
  .feature-list li { grid-template-columns: 15rem 1fr; }
}

/* Each feature is a subsection of the page's h1, so it is an h2 for screen readers, sized
   down to sit as a row label. */
.feature-list h2 {
  font-size: 1.05rem;
  line-height: 1.25;
  font-stretch: 106%;
}

/* Title and body are side-by-side columns here, so the general heading spacing rule would
   push the body out of line with its title. */
.feature-list h2 + p { margin-top: 0; }
.feature-list p { color: var(--muted); font-size: 0.97rem; max-width: none; }

/* ---- privacy ----------------------------------------------------------- */

.privacy-intro {
  display: grid;
  gap: 1.4rem clamp(2rem, 5vw, 4rem);
  align-items: start;
}

@media (min-width: 54rem) {
  .privacy-intro { grid-template-columns: 1.15fr 0.85fr; }
}

.privacy-lead {
  font-stretch: 112%;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.1;
  font-size: clamp(1.6rem, 3.6vw, 2.35rem);
  max-width: 20ch;
  margin-bottom: 0;
}

.privacy-points {
  list-style: none;
  margin: 2.4rem 0 0;
  padding: 0;
  display: grid;
  gap: 1.6rem;
}

@media (min-width: 52rem) {
  .privacy-points { grid-template-columns: repeat(3, 1fr); gap: 2.4rem; }
}

.privacy-points dt {
  font-stretch: 106%;
  font-weight: 700;
  margin-bottom: 0.3rem;
}

.privacy-points dd { margin: 0; color: var(--muted); font-size: 0.95rem; }

/* ---- plain lists ------------------------------------------------------- */

.plain {
  margin: 2rem 0 0;
  padding: 0;
  list-style: none;
}

.plain li {
  padding-block: 1.1rem;
  border-top: 1px solid var(--border);
  color: var(--muted);
  max-width: 46rem;
}

.plain b { color: var(--text); font-weight: 700; }

/* ---- footer ------------------------------------------------------------ */

/* Pinned to the bottom of the window on every page. Fixed rather than sticky, because a
   sticky footer only pins once there is content below it to hold it there. Body carries
   a matching padding-bottom, since a fixed element takes up no space of its own. */
.site-footer {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 50;
  min-height: var(--footbar);
  border-top: 1px solid var(--border);
  background: color-mix(in oklab, var(--surface) 70%, var(--bg));
  padding-bottom: env(safe-area-inset-bottom, 0px);
  color: var(--muted);
  font-size: 0.9rem;
}

/* Right-aligned, so the links sit under the Get the app button: both are in the same
   .wrap, so both end at its right edge. */
.site-footer .wrap {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  min-height: var(--footbar);
}

.site-footer nav { display: flex; gap: 1.8rem; flex-wrap: wrap; justify-content: flex-end; }

/* Blue, but without the underline of a link sitting in a paragraph. */
.site-footer nav a { color: var(--accent); text-decoration: none; }

/* Hover lights them up like the point where the wave's lines cross: white, with the same
   sky blue bleeding out of it. The glow colour is the wave's own brightest stop. */
.site-footer nav a:hover {
  color: #ffffff;
  text-shadow:
    0 0 9px rgba(125, 211, 252, 0.85),
    0 0 20px rgba(59, 130, 246, 0.55);
}

/* In light mode the bar is nearly white, so white text would simply disappear. Same idea —
   it lights up and glows — in the one direction that can be seen against it. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .site-footer nav a:hover {
    color: color-mix(in oklab, var(--accent) 72%, #000000);
    text-shadow: 0 0 12px rgba(59, 130, 246, 0.45);
  }
}

:root[data-theme="light"] .site-footer nav a:hover {
  color: color-mix(in oklab, var(--accent) 72%, #000000);
  text-shadow: 0 0 12px rgba(59, 130, 246, 0.45);
}

/* The page you are on is named here too, the same way the header names it. */
.site-footer nav a[aria-current="page"] { font-weight: 700; }

/* ---- document pages ---------------------------------------------------- */

.doc { padding-block: clamp(2.5rem, 6vw, 4rem); }
.doc .wrap { width: min(44rem, 100% - 2.5rem); }
.doc h1 { font-size: clamp(2rem, 5vw, 2.9rem); margin-bottom: 0.8rem; }
.doc h2 { font-size: 1.4rem; margin-top: 2.8rem; margin-bottom: 0.7rem; }
.doc h3 { font-size: 1.05rem; margin-top: 1.8rem; margin-bottom: 0.4rem; }
.doc p, .doc li { max-width: none; }
.doc ul { padding-left: 1.15rem; margin: 0 0 1rem; }
.doc li { margin-bottom: 0.5rem; }

.doc-meta {
  color: var(--muted);
  font-size: 0.9rem;
  padding-bottom: 1.6rem;
  border-bottom: 1px solid var(--border);
  margin-bottom: 1.8rem;
}

.callout {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.3rem 1.5rem;
  margin: 1.6rem 0;
}

.callout p:last-child { margin-bottom: 0; }

.placeholder {
  background: linear-gradient(90deg, var(--grad-a), var(--grad-b));
  color: #fff;
  padding: 0.1rem 0.45rem;
  border-radius: 5px;
  font-weight: 700;
  font-size: 0.92em;
}

.faq { margin-top: 2.5rem; }
.faq h2 { margin-top: 2.4rem; }

/* ---- the hero demo ----------------------------------------------------- */

/* The screenshot is the no-JavaScript fallback; the live phone replaces it in place. */
.hero-demo.is-live { display: grid; }
/* An invitation, not a disclaimer — accent so it reads as "this thing is live".
   Deliberately not a button: what you tap is the phone above it, not this line. */
.hero-demo .qb-hint { color: var(--accent); font-size: 0.85rem; }

/* The phone and its hint sit on the wrap's right edge, so they line up with the App Store
   badge in the header and Support in the footer — all three share that edge.
   The stage centres the phone and pads it on all four sides by default; here it is pushed
   to the end of its column and the right pad is dropped, or the phone would stop 19px
   short of the badge above it. The hint's auto margins do the same job on the same edge. */
.hero-demo .qb-stage {
  place-items: center end;
  padding-right: 0;
}

/* Centred under the phone, not under the column. The phone sits flush against the column's
   right edge, so a box exactly as wide as the phone and pushed to that same edge lands
   directly beneath it - and --phone-w comes from phone.js, which is the only place the
   rendered width is known, and is kept up to date by a ResizeObserver. */
.hero-demo .qb-hint {
  width: var(--phone-w, auto);
  max-width: none;
  margin-left: auto;
  margin-right: 0;
  text-align: center;
}

/* ---- the wave ---------------------------------------------------------- */

/* A ribbon of lines behind the top of each page — on the home page, behind the phone.
   It is a layer, not a block: it takes no space and never moves the content.
   Decoration only: with no JavaScript nothing draws and the layer stays empty, so no page
   loses anything. */
.has-wave { position: relative; }

/* Everything the page actually says sits above it. */
.has-wave > .wrap,
.has-wave > * { position: relative; z-index: 1; }

.wave {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: clamp(260px, 40vw, 520px);
  transform: translateY(-50%);
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}

/* Only the hero has the room for it. Everywhere else the page is dense text, and a ribbon
   running through a paragraph is just harder to read — so on those pages it stays up in
   the title block's own white space and fades out before the copy begins. */
.has-wave:not(.hero) > .wave {
  top: 0;
  height: clamp(180px, 24vw, 300px);
  transform: none;
}

.has-wave:not(.hero) > .wave canvas {
  -webkit-mask-image:
    linear-gradient(90deg, transparent, #000 17%, #000 83%, transparent),
    linear-gradient(180deg, #000 38%, transparent 94%);
  mask-image:
    linear-gradient(90deg, transparent, #000 17%, #000 83%, transparent),
    linear-gradient(180deg, #000 38%, transparent 94%);
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}

/* A privacy policy and a help page are read closely, and their dateline is small grey
   text — the worst thing to run lines through. Here the ribbon clears out above it,
   behind the title and the empty space beside it, and nothing else. */
.doc.has-wave > .wave { height: clamp(150px, 19vw, 250px); }

.doc.has-wave > .wave canvas {
  -webkit-mask-image:
    linear-gradient(90deg, transparent, #000 17%, #000 83%, transparent),
    linear-gradient(180deg, #000 18%, transparent 58%);
  mask-image:
    linear-gradient(90deg, transparent, #000 17%, #000 83%, transparent),
    linear-gradient(180deg, #000 18%, transparent 58%);
}

/* The lines fade out at the edges rather than stopping at a cut. This masks the canvas
   itself: painting a background-coloured gradient over the top instead would draw a
   visible rectangle wherever the page behind it is not exactly that colour, which on the
   hero's own gradient it never is. */
.wave canvas {
  display: block;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 17%, #000 83%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 17%, #000 83%, transparent);
}

/* The glow, in dark mode only. wave.js draws this layer at a quarter of the pixels and
   this blur covers for it — cheap where ctx.shadowBlur, which blurs all 24 strokes
   individually every frame, was the most expensive thing on the page. */
.wave-glow {
  filter: blur(6px);
  opacity: 0.9;
}

/* On a narrow screen the same fade eats most of the ribbon, and the phone covers the
   rest, so it barely shows at all. Pull the fade in to the very edges. */
@media (max-width: 48rem) {
  .wave canvas {
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
    mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
  }
}

/* ---- straight answers: the reader card --------------------------------- */

/* The list is capped at a 46rem measure so it stays readable, which on a wide screen leaves
   the right-hand half empty. This puts the one question that actually stops an install —
   what the accessibility service can see — next to the line that raises it. Below 64rem
   there is no room for two columns, so the card falls under the list. */
.answers-grid {
  display: grid;
  gap: 2rem;
  align-items: start;
}

@media (min-width: 64rem) {
  .answers-grid {
    grid-template-columns: minmax(0, 46rem) minmax(17rem, 22rem);
    gap: 3rem;
  }
}

.reader-card {
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
  padding: 1.4rem 1.5rem;
  color: var(--muted);
  font-size: 0.92rem;
}

.reader-card h2 {
  margin: 0 0 1rem;
  color: var(--text);
  font-size: 1.15rem;
}

/* "It can see" / "It cannot see" — labels for the two lists, not headings in their own
   right, so they are sized as small caps rather than as another level of the page. */
.reader-head {
  margin: 1rem 0 0.4rem;
  color: var(--text);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.reader-card ul {
  margin: 0;
  padding-left: 1.1rem;
  display: grid;
  gap: 0.45rem;
}

.reader-card li { line-height: 1.45; }

/* The package name is the detail that makes the claim checkable, so it is set apart. */
.reader-pkg {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.85em;
  color: var(--text);
  word-break: break-all;
}

.reader-foot {
  margin: 1.2rem 0 0;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
  font-size: 0.85rem;
}
