
  
  
  :root {
    color-scheme: dark;
    --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    --mono: "SF Mono", ui-monospace, "JetBrains Mono", "Roboto Mono", Menlo, Consolas, monospace;
    --display: 'LSCond', Oswald, "Arial Narrow", "Segoe UI", sans-serif;
    /* Numbers only — lap times, deltas, prices. Never prose: --mono keeps
       the spec-sheet voice for the comparison table and legal lines. */
    --numeric: 'LSNum', "Roboto Condensed", ui-monospace, monospace;

    --bg: #12151A;
    --surface: #1B2029;
    --surface-2: #242A34;
    --surface-3: #2E3540;
    --ink: #EEF1F6;
    --muted: #8A93A8;
    --line: rgba(238,241,246,.09);
    --line-strong: rgba(238,241,246,.18);
    --accent: #38BDF8;
    --accent-ink: #062033;
    --energy: #F97316;
    --energy-ink: #221000;
    --good: #35C97C;
    --warn: #F5B942;
    --bad: #FF5B5B;
    --sp1: #38BDF8; --sp2: #35C97C; --sp3: #F5B942; --sp4: #FF5B5B;
    --shadow: 0 1px 0 rgba(0,0,0,.5), 0 24px 60px rgba(0,0,0,.4);
    --radius: 8px;
    --radius-md: 10px;
    --radius-lg: 12px;
    /* Type ladder, shared with the app (LapperType: micro/label/body/base).
       Four steps at least 2px apart — the site previously had twelve sizes
       inside a 6px range, which reads as one flat wash rather than a
       hierarchy. Display sizes stay on clamp() below. */
    --fs-micro: 11px;   /* uppercase meta: eyebrows, table heads, badges */
    --fs-label: 13px;   /* buttons, dense UI text */
    --fs-body:  15px;   /* card copy, lists, prose */
    --fs-base:  17px;   /* body */
    /* One tracking value for every uppercase label. Negative tracking on
       display sizes (h1/h2/.price) is an optical correction and stays. */
    --track-caps: .08em;
    --maxw: 1200px;
    /* Brand logo — LAPPER flips with the theme; track + SNAPPER cyan and the
       orange start dot stay constant */
    --logo-lapper:#e6ebf2;
  }
  @media (prefers-color-scheme: light) {
    :root {
      color-scheme: light;
      --bg: #E0E0E0;
      --surface: #FFFFFF;
      --surface-2: #E9EDF5;
      --surface-3: #DCE2ED;
      --ink: #12151A;
      --muted: #4B5364;
      --line: rgba(18,21,26,.10);
      --line-strong: rgba(18,21,26,.20);
      --accent: #075985;
      --accent-ink: #FFFFFF;
      --energy: #B03B0B;
      --energy-ink: #FFFFFF;
      --good: #0B7141;
      --warn: #8A5200;
      --bad: #B4352A;
      --sp1: #075985; --sp2: #0B7141; --sp3: #8A5200; --sp4: #B4352A;
      --shadow: 0 1px 0 rgba(18,21,26,.05), 0 20px 50px rgba(18,21,26,.08);
      --logo-lapper:#333333;
    }
  }
  :root[data-theme="dark"] {
    color-scheme: dark;
    --bg:#12151A; --surface:#1B2029; --surface-2:#242A34; --surface-3:#2E3540;
    --ink:#EEF1F6; --muted:#8A93A8;
    --line:rgba(238,241,246,.09); --line-strong:rgba(238,241,246,.18);
    --accent:#38BDF8; --accent-ink:#062033; --energy:#F97316; --energy-ink:#221000;
    --good:#35C97C; --warn:#F5B942; --bad:#FF5B5B;
    --sp1:#38BDF8; --sp2:#35C97C; --sp3:#F5B942; --sp4:#FF5B5B;
    --shadow:0 1px 0 rgba(0,0,0,.5), 0 24px 60px rgba(0,0,0,.4);
    --logo-lapper:#e6ebf2;
  }
  :root[data-theme="light"] {
    color-scheme: light;
    --bg:#E0E0E0; --surface:#FFFFFF; --surface-2:#E9EDF5; --surface-3:#DCE2ED;
    --ink:#12151A; --muted:#4B5364;
    --line:rgba(18,21,26,.10); --line-strong:rgba(18,21,26,.20);
    --accent:#075985; --accent-ink:#FFFFFF; --energy:#B03B0B; --energy-ink:#FFFFFF;
    --good:#0B7141; --warn:#8A5200; --bad:#B4352A;
    --sp1:#075985; --sp2:#0B7141; --sp3:#8A5200; --sp4:#B4352A;
    --shadow:0 1px 0 rgba(18,21,26,.05), 0 20px 50px rgba(18,21,26,.08);
    --logo-lapper:#333333;
  }

  * { box-sizing: border-box; }
  html { scroll-behavior: smooth; }
  body {
    margin: 0; background: var(--bg); color: var(--ink);
    font-family: var(--sans); font-size: var(--fs-base); line-height: 1.6;
    -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
  }
  @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

  .wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 24px; }
  .mono { font-family: var(--mono); }
  .eyebrow {
    font-family: var(--display); font-size: var(--fs-micro); letter-spacing: var(--track-caps);
    text-transform: uppercase; color: var(--accent); font-weight: 600;
  }
  /* Start-dot motif, echoing the logo's orange start dot. Orange (--energy) is
     deliberately scarce: this dot and the hero delta chip are the only places it
     appears. It is never an interactive color — interactive stays cyan. */
  .eyebrow::before {
    content: ""; display: inline-block; width: 7px; height: 7px; border-radius: 50%;
    background: var(--energy); margin-right: 9px; vertical-align: 1px;
  }
  h1, h2, h3 { text-wrap: balance; line-height: 1.08; margin: 0; letter-spacing: -.01em; font-weight: 600;
    font-family: var(--display); }
  h1 { font-size: clamp(44px, 9vw, 96px); line-height: 1.04; letter-spacing: -.015em; }
  h2 { font-size: clamp(36px, 4.5vw, 56px); }
  h3 { font-size: 1.24rem; letter-spacing: 0; font-weight: 600; }
  p { margin: 0; }
  a { color: inherit; }
  .lead { font-size: clamp(1.05rem, 1.7vw, 1.28rem); color: var(--muted); max-width: 60ch; font-family: var(--sans); }
  .muted { color: var(--muted); }
  :focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }

  /* ---- header ---- */
  header {
    position: sticky; top: 0; z-index: 50;
    backdrop-filter: saturate(1.4) blur(12px);
    background: color-mix(in srgb, var(--bg) 84%, transparent);
    border-bottom: 1px solid var(--line);
  }
  .nav { display: flex; align-items: center; gap: 22px; height: 96px; }
  .brand { display: flex; align-items: center; gap: 10px; font-family: var(--display); font-weight: 700;
    letter-spacing: var(--track-caps); text-transform: uppercase; font-size: var(--fs-body); text-decoration: none; cursor: pointer; white-space: nowrap; }
  .brand .tick { width: 26px; height: 14px; border-radius: 2px; overflow: hidden; display: inline-flex; }
  .brand .tick i { flex: 1; }
  .brand .logo { height: 44px; width: auto; display: block; }
  /* Width-driven, not a fixed height: in the 1fr 1fr mobile footer grid a fixed
     30px height pins the intrinsic 229.5px width and overflows a 320px viewport.
     min(230px, 100%) keeps the desktop size and shrinks with the column. */
  .foot .brand .logo { width: min(230px, 100%); height: auto; }
  .logo .logo-lapper { fill: var(--logo-lapper); }
  .nav-links { display: flex; gap: 22px; margin-left: 8px; }
  .nav-links a {
    font-family: var(--display); font-size: var(--fs-label); letter-spacing: var(--track-caps); text-transform: uppercase; font-weight: 600;
    text-decoration: none; color: var(--muted); cursor: pointer; padding: 6px 0; border-bottom: 2px solid transparent;
  }
  .nav-links a:hover { color: var(--ink); }
  .nav-links a.mobile-cta { display: none; }
  .nav-links a.active { color: var(--ink); border-bottom-color: var(--accent); }
  .nav-spacer { flex: 1; }
  .icon-btn { background: none; border: 1px solid var(--line); color: var(--ink); width: 44px; height: 44px;
    border-radius: var(--radius); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; font-size: var(--fs-body); }
  .icon-btn:hover { border-color: var(--line-strong); }

  /* Language switcher. One instance, beside the theme toggle, at every breakpoint —
     CSS cannot reparent a <details> into the mobile menu, and moving it with JS would
     break the disclosure's zero-JS promise. */
  .langswitch { position: relative; }
  .langswitch > summary { list-style: none; cursor: pointer; width: auto; padding: 0 10px; gap: 6px; }
  .langswitch > summary::-webkit-details-marker { display: none; }
  .langswitch .flag { width: 16px; height: 12px; border-radius: 2px; box-shadow: 0 0 0 1px var(--line); }
  .langswitch .globe { width: 16px; height: 16px; color: var(--muted); }
  .langswitch nav { position: absolute; right: 0; top: calc(100% + 8px); background: var(--bg);
    border: 1px solid var(--line); border-radius: var(--radius); padding: 6px; min-width: 168px; z-index: 30; }
  .langswitch ul { list-style: none; margin: 0; padding: 0; }
  .langswitch a { display: flex; align-items: center; gap: 10px; padding: 8px 10px;
    border-radius: var(--radius); font-size: var(--fs-label); text-decoration: none; color: inherit; white-space: nowrap; }
  .langswitch a:hover { background: var(--line); }
  .langswitch a[aria-current] { font-weight: 700; }
  /* At the narrowest widths only the icon shows — which is why aria-label carries the
     meaning on the <summary>. */
  @media (max-width: 420px) { .langswitch > summary span { display: none; } }
  .btn {
    font-family: var(--display); font-size: var(--fs-label); letter-spacing: var(--track-caps); text-transform: uppercase; font-weight: 700;
    padding: 11px 18px; border-radius: var(--radius); border: 1px solid transparent; cursor: pointer; text-decoration: none;
    display: inline-flex; align-items: center; gap: 8px; white-space: nowrap; transition: transform .08s ease, background .15s ease;
  }
  .btn:active { transform: translateY(1px); }
  .btn-primary { background: var(--accent); color: var(--accent-ink); }
  .btn-primary:hover { background: color-mix(in srgb, var(--accent) 88%, #000); }
  .btn-ghost { background: transparent; color: var(--ink); border-color: var(--line-strong); }
  .btn-ghost:hover { background: var(--surface-2); }
  .btn-lg { padding: 14px 24px; font-size: var(--fs-body); }
  .menu-btn { display: none; }

  /* ---- generic sections ---- */
  main { display: block; }
  /* There is no router and no stacking anymore — one page per document, and the build
     stamps it .active before writing the file, so .page.active (same specificity,
     declared next) always wins here. Kept as the .js-gated default every other
     pre-hidden state uses, in case that ever stops being true. */
  .js .page { display: none; }
  .page.active { display: block; animation: fade .3s ease; }
  @keyframes fade { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
  @media (prefers-reduced-motion: reduce) { .page.active { animation: none; } }
  section.band { padding: 84px 0; border-top: 1px solid var(--line); }
  section.band:first-child { border-top: none; }
  /* The answer and final-CTA bands sit a step above the page: a whisper of surface
     tint and more air. Restrained on purpose — the palette stays untouched. */
  section.band.elevated { background: color-mix(in srgb, var(--surface) 55%, var(--bg)); padding: 100px 0; }
  .center { text-align: center; }
  /* .btn's display:inline-flex would otherwise beat the UA's [hidden] rule. */
  [hidden] { display: none !important; }
  .stack-sm > * + * { margin-top: 14px; }
  .cta-row { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
  .cta-row.center { justify-content: center; }

  /* ---- hero ---- */
  .hero {
    position: relative; overflow: hidden; padding: 56px 0 88px; border-top: none;
    background:
      radial-gradient(680px 380px at 8% -10%, color-mix(in srgb, var(--accent) 16%, transparent), transparent 65%),
      radial-gradient(620px 360px at 106% 106%, color-mix(in srgb, var(--energy) 14%, transparent), transparent 65%);
  }
  /* ---- photographic bands ----
     One treatment for every photo on the site, so the imagery reads as a set.
     A decorative plate fills the band; the section's own background doubles as
     the paint-before-load fallback. Every scrim mixes from var(--bg), which is
     what lets a single implementation grade correctly in both themes, and every
     one lands on solid var(--bg) at the bottom edge so the band blends into the
     page rather than ending on a hard seam. Each band then tunes its own
     gradient to suit how its copy is laid out. */
  .photo-bg { position: absolute; inset: 0; }
  .photo-bg img { width: 100%; height: 100%; object-fit: cover; }
  .photo-bg::after { content: ""; position: absolute; inset: 0; }
  /* Bands that carry a plate — or the schematic grid below — need to contain it and
     stack their copy above it. */
  .band.has-photo, .band.schematic { position: relative; overflow: hidden; }
  .band.has-photo > .wrap, .band.schematic > .wrap { position: relative; z-index: 1; }

  /* The privacy band is the only major section with no plate, and it should stay that
     way: a photograph behind a data-residency table would undercut a claim that is
     technical rather than emotional. But it sits between two plain bands on the same
     --bg, so three sections run flat through the middle of the page. It gets tone plus
     a faint schematic grid instead — its own eyebrow reads "Private by architecture"
     and its right column is already a mono diagram, so graph paper is the substrate the
     content implies. Pure CSS, so it adds no bytes and leaves photography special. The
     mask fades the grid out before the edges so it never reads as a hard-edged box. */
  .band.schematic { background: color-mix(in srgb, var(--surface) 62%, var(--bg)); }
  .band.schematic::before {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    background:
      repeating-linear-gradient(90deg, var(--line) 0 1px, transparent 1px 56px),
      repeating-linear-gradient(180deg, var(--line) 0 1px, transparent 1px 56px);
    -webkit-mask-image: radial-gradient(125% 105% at 50% 50%, #000 35%, transparent 86%);
    mask-image: radial-gradient(125% 105% at 50% 50%, #000 35%, transparent 86%);
  }

  /* Hero: copy is left-aligned, so the veil is heaviest over the headline column
     and clears toward the top-right where the peaks sit. The art is far wider
     than the hero box, so cover scales it by height and trims the sides; biasing
     X past centre keeps those peaks behind the phone rather than losing them to
     that trim. Under 900px the copy spans the full width, so the veil evens out. */
  .hero-bg img { object-position: 64% 50%; }
  /* The two washes stack, so their effective opacity multiplies: the dark-theme values
     below come to ~77% over the headline column, which is why a light theme frosted the
     art over. The stops are named so a light theme can restate the five numbers rather
     than the whole gradient — light copy on this plate is all --ink or the darkened
     eyebrow, which leaves plenty of contrast headroom to spend on showing the photo. */
  .hero-bg::after {
    --v1: 62%; --v2: 28%; --v3: 10%;
    --w1: 32%; --w2: 52%;
    background:
      linear-gradient(90deg, color-mix(in srgb, var(--bg) var(--v1), transparent), color-mix(in srgb, var(--bg) var(--v2), transparent) 62%, color-mix(in srgb, var(--bg) var(--v3), transparent)),
      linear-gradient(180deg, color-mix(in srgb, var(--bg) var(--w1), transparent), color-mix(in srgb, var(--bg) var(--w2), transparent) 55%, var(--bg) 98%);
  }
  @media (prefers-color-scheme: light) { .hero-bg::after { --v1: 44%; --v2: 18%; --v3: 6%; --w1: 16%; --w2: 26%; } }
  :root[data-theme="light"] .hero-bg::after { --v1: 44%; --v2: 18%; --v3: 6%; --w1: 16%; --w2: 26%; }
  :root[data-theme="dark"] .hero-bg::after { --v1: 62%; --v2: 28%; --v3: 10%; --w1: 32%; --w2: 52%; }
  @media (max-width: 900px) {
    .hero-bg::after { background:
      linear-gradient(180deg, color-mix(in srgb, var(--bg) var(--m1), transparent), color-mix(in srgb, var(--bg) var(--m2), transparent) 55%, var(--bg) 98%);
      --m1: 64%; --m2: 76%;
    }
    /* Mobile cannot take the desktop lightening: at this crop the eyebrow lands on
       shadowed trees, and measured there 48% left it at 2.98:1. 66% is the floor. */
    @media (prefers-color-scheme: light) { .hero-bg::after { --m1: 66%; --m2: 70%; } }
    :root[data-theme="light"] .hero-bg::after { --m1: 66%; --m2: 70%; }
    :root[data-theme="dark"] .hero-bg::after { --m1: 64%; --m2: 76%; }
  }
  /* Answer band: copy is left-aligned like the hero's, so the veil is heaviest
     there and thins toward the windscreen on the right. It needs to be stronger
     than the hero's, because this plate's bright centre (sky and sunlit trees)
     sits directly behind the copy rather than beside it. Narrow screens drop the
     horizontal weighting — cover zooms in far enough that the copy can land on
     any part of the frame, so the veil has to be even and much deeper. */
  .answer-bg img { object-position: 50% 46%; }
  /* Same stacking, and the heaviest on the site: ~84% effective over the copy in a dark
     theme. Light themes restate the stops for the same reason as the hero. */
  .answer-bg::after {
    --v1: 74%; --v2: 42%; --v3: 24%;
    --w1: 34%; --w2: 52%;
    background:
      linear-gradient(90deg, color-mix(in srgb, var(--bg) var(--v1), transparent), color-mix(in srgb, var(--bg) var(--v2), transparent) 60%, color-mix(in srgb, var(--bg) var(--v3), transparent)),
      linear-gradient(180deg, color-mix(in srgb, var(--bg) var(--w1), transparent), color-mix(in srgb, var(--bg) var(--w2), transparent) 58%, var(--bg) 99%);
  }
  @media (prefers-color-scheme: light) { .answer-bg::after { --v1: 50%; --v2: 24%; --v3: 12%; --w1: 16%; --w2: 28%; } }
  :root[data-theme="light"] .answer-bg::after { --v1: 50%; --v2: 24%; --v3: 12%; --w1: 16%; --w2: 28%; }
  :root[data-theme="dark"] .answer-bg::after { --v1: 74%; --v2: 42%; --v3: 24%; --w1: 34%; --w2: 52%; }
  @media (max-width: 900px) {
    .answer-bg::after { background:
      linear-gradient(180deg, color-mix(in srgb, var(--bg) var(--m1), transparent), color-mix(in srgb, var(--bg) var(--m2), transparent) 55%, var(--bg) 99%);
      --m1: 80%; --m2: 87%;
    }
    @media (prefers-color-scheme: light) { .answer-bg::after { --m1: 56%; --m2: 68%; } }
    :root[data-theme="light"] .answer-bg::after { --m1: 56%; --m2: 68%; }
    :root[data-theme="dark"] .answer-bg::after { --m1: 80%; --m2: 87%; }
  }
  /* Chapters: ONE plate spans all four chapters, so scrolling the run follows a
     single unbroken ribbon of track. The continuity is structural — the four
     chapters already share a parent band — so this needs no scroll effect and no
     background-attachment: fixed, which janks on iOS. Copy alternates left and
     right from chapter to chapter, so the veil cannot favour either side: it
     stays even across the width and fades to solid --bg at the top and bottom
     edges so the run joins its neighbouring bands seamlessly. The plate is
     already near-monochrome, so a mid-strength veil is enough to keep it quiet
     while still letting the asphalt read behind the copy. */
  /* --veil has to be theme-aware here, unlike the other two plates. This one is a
     DARK photograph, so a light theme mixes it toward --bg and lifts it *toward*
     the ink colour instead of away from it. Measured against the plate's darkest
     pixels: at 60% the 12px accent eyebrow falls to 2.3:1, and only ~88% brings it
     back over the 4.5:1 floor. Dark themes are insensitive to the value (16:1 ink,
     8.5:1 eyebrow at any strength), so they keep the asphalt fully present. */
  .chapters-bg::after {
    --veil: 60%;
    background:
      linear-gradient(180deg, var(--bg), color-mix(in srgb, var(--bg) var(--veil), transparent) 5%, color-mix(in srgb, var(--bg) var(--veil), transparent) 95%, var(--bg));
  }
  /* This is the one plate delivered as a CSS background rather than a <picture>,
     because it is the one plate that must not reach phones at all. Below 900px the
     chapters stack, taking the band to ~11x taller than wide; cover then zooms ~5x
     into a narrow centre strip, the ribbon leaves the frame and only flat tone is
     left. Hiding an <img> does not stop the download — measured, Chrome still
     transferred the full 70KB with the wrapper at display:none — whereas a
     background-image whose URL is only ever declared above the breakpoint cannot
     be fetched below it. image-set() keeps the AVIF/JPEG choice and the 1x/1.5x
     step that srcset was giving us. */
  .chapters-bg { background-position: 50% 50%; background-size: cover; background-repeat: no-repeat; }
  @media (min-width: 901px) {
    .chapters-bg {
      background-image: image-set(
        url("/img/chapters-aerial-1408.avif") type("image/avif") 1x,
        url("/img/chapters-aerial-2112.avif") type("image/avif") 1.5x,
        url("/img/chapters-aerial-1408.jpg") type("image/jpeg") 1x);
    }
  }
  @media (prefers-color-scheme: light) { .chapters-bg::after { --veil: 70%; } }
  :root[data-theme="light"] .chapters-bg::after { --veil: 70%; }
  :root[data-theme="dark"] .chapters-bg::after { --veil: 60%; }

  /* Pricing: the plate is a banner across the TOP of the band, not the whole of it.
     The plan cards below are opaque --surface panels, so any image behind them would
     be paid for and never seen; the veil lands on solid --bg before they begin. Like
     the chapters plate this one is a dark photograph, so its veil is theme-aware for
     the same measured reason: against its darkest pixels the 12px accent eyebrow needs
     ~88% in a light theme to clear 4.5:1, while dark themes are fine at 58%. */
  .pricing-bg { bottom: auto; height: 920px; }
  .pricing-bg img { object-position: 50% 58%; }
  .pricing-bg::after {
    --veil: 58%;
    background:
      linear-gradient(180deg, color-mix(in srgb, var(--bg) var(--veil), transparent), color-mix(in srgb, var(--bg) calc(var(--veil) + 12%), transparent) 55%, var(--bg) 97%);
  }
  @media (prefers-color-scheme: light) { .pricing-bg::after { --veil: 70%; } }
  :root[data-theme="light"] .pricing-bg::after { --veil: 70%; }
  :root[data-theme="dark"] .pricing-bg::after { --veil: 58%; }
  @media (max-width: 900px) { .pricing-bg { height: 860px; } }

  /* --muted copy measures only ~4.5:1 against a bare light --bg, so ANY plate behind
     it drops it under the floor: measured, the hero lead lands at 3.7:1 and the
     answer lead at 3.2:1. Light themes therefore promote copy that sits on a plate
     to --ink, which measures 10:1 or better over the same veiled pixels. Dark themes
     need no change — --muted over a veiled plate still measures ~6:1 there — so the
     hierarchy they were designed with is left exactly as it is. */
  @media (prefers-color-scheme: light) {
    .hero .lead, .hero .qualify, .band.has-photo .lead { color: var(--ink); }
  }
  :root[data-theme="light"] .hero .lead,
  :root[data-theme="light"] .hero .qualify,
  :root[data-theme="light"] .band.has-photo .lead { color: var(--ink); }
  :root[data-theme="dark"] .hero .lead,
  :root[data-theme="dark"] .hero .qualify,
  :root[data-theme="dark"] .band.has-photo .lead { color: var(--muted); }

  /* Teaser: a wide, short banner. Copy sits in the left 45%, so the veil is heaviest
     there and clears toward the karts right of centre — the hero's arrangement. The
     plate needs no vertical fade to --bg because the band is short enough that the
     horizontal wash already carries it. */
  .teaser-bg::after {
    background:
      linear-gradient(90deg, color-mix(in srgb, var(--bg) 78%, transparent), color-mix(in srgb, var(--bg) 46%, transparent) 58%, color-mix(in srgb, var(--bg) 22%, transparent)),
      linear-gradient(180deg, color-mix(in srgb, var(--bg) 30%, transparent), color-mix(in srgb, var(--bg) 44%, transparent) 70%, color-mix(in srgb, var(--bg) 68%, transparent));
  }
  @media (max-width: 900px) {
    .teaser-bg::after { background:
      linear-gradient(180deg, color-mix(in srgb, var(--bg) 78%, transparent), color-mix(in srgb, var(--bg) 86%, transparent) 60%, var(--bg) 99%);
    }
  }

  /* About: prose sits in a centred column, so unlike every other band there is no
     side to bias the veil toward — the copy runs straight down the middle of the
     frame. The veil is therefore even across the width and deeper overall than the
     rest, with the plate reading as atmosphere behind a page of text rather than as
     a picture in its own right. */
  .about-bg img { object-position: 50% 42%; }
  .about-bg::after {
    --veil: 74%;
    background:
      linear-gradient(180deg, color-mix(in srgb, var(--bg) calc(var(--veil) - 8%), transparent), color-mix(in srgb, var(--bg) var(--veil), transparent) 45%, color-mix(in srgb, var(--bg) calc(var(--veil) + 10%), transparent) 88%, var(--bg));
  }
  @media (prefers-color-scheme: light) { .about-bg::after { --veil: 72%; } }
  :root[data-theme="light"] .about-bg::after { --veil: 72%; }
  :root[data-theme="dark"] .about-bg::after { --veil: 74%; }

  /* Guides: same job as About — atmosphere behind a page of reading, not a picture.
     The plate's one bright region (the low sun, upper-left) sits directly behind the
     copy column, so on top of the even vertical veil this band borrows the hero's
     side-weighted layer: heaviest over the left column, clear of the kart right of
     centre. Dark needs it at full strength (measured: the eyebrow landed at 3.1:1
     against the haze without it); light passes on the vertical veil alone, so its
     side layer is kept light to preserve the plate. Cards are opaque --surface. */
  .guides-bg img { object-position: 50% 40%; }
  .guides-bg::after {
    --veil: 74%;
    --side: 78%;
    background:
      linear-gradient(90deg, color-mix(in srgb, var(--bg) var(--side), transparent), transparent 55%),
      linear-gradient(180deg, color-mix(in srgb, var(--bg) calc(var(--veil) - 6%), transparent), color-mix(in srgb, var(--bg) var(--veil), transparent) 45%, color-mix(in srgb, var(--bg) calc(var(--veil) + 12%), transparent) 88%, var(--bg));
  }
  @media (prefers-color-scheme: light) { .guides-bg::after { --veil: 76%; --side: 40%; } }
  :root[data-theme="light"] .guides-bg::after { --veil: 76%; --side: 40%; }
  :root[data-theme="dark"] .guides-bg::after { --veil: 74%; --side: 78%; }

  /* Contact: the copy column is short and sits upper-left, and the form is an opaque
     --surface card over the right half — so the only place the plate is really seen is
     the empty lower-left quadrant. The veil is therefore weighted to the TOP, where
     the copy sits over near-black night sky, and eases off toward the bottom where the
     wet-tarmac reflections do the work. Theme-aware like the other dark plates. */
  .contact-bg img { object-position: 50% 55%; }
  .contact-bg::after {
    --veil: 58%;
    background:
      linear-gradient(180deg, color-mix(in srgb, var(--bg) calc(var(--veil) + 14%), transparent), color-mix(in srgb, var(--bg) var(--veil), transparent) 58%, color-mix(in srgb, var(--bg) calc(var(--veil) - 6%), transparent));
  }
  @media (prefers-color-scheme: light) { .contact-bg::after { --veil: 72%; } }
  :root[data-theme="light"] .contact-bg::after { --veil: 72%; }
  :root[data-theme="dark"] .contact-bg::after { --veil: 58%; }
  /* Was an inline color:var(--accent), which no rule could override — it has to be a
     class so it can darken over a plate in light themes, exactly like the eyebrow. */
  .contact-email { color: var(--accent); text-decoration: none; }

  /* The 12px accent eyebrow was what forced the light veils up to 86-88%, which read
     as too much frosting over the art. Darkening the label itself in light themes buys
     the same contrast far more cheaply: measured against a plate's darkest pixels,
     --accent needs a ~88% veil to clear 4.5:1, while this mix clears it at 70%. Only
     light themes need it — dark themes put a bright label on a dark plate and already
     measure 8:1+ — and only the label, since the surrounding copy is already --ink. */
  @media (prefers-color-scheme: light) {
    .hero .eyebrow, .band.has-photo .eyebrow, .band.has-photo .contact-email { color: color-mix(in srgb, var(--accent) 58%, #000); }
  }
  :root[data-theme="light"] .hero .eyebrow,
  :root[data-theme="light"] .band.has-photo .eyebrow,
  :root[data-theme="light"] .band.has-photo .contact-email { color: color-mix(in srgb, var(--accent) 58%, #000); }
  :root[data-theme="dark"] .hero .eyebrow,
  :root[data-theme="dark"] .band.has-photo .eyebrow,
  :root[data-theme="dark"] .band.has-photo .contact-email { color: var(--accent); }

  /* Bright plates are the exception to the rule above. The pricing plate carries
     bright specular metal and the teaser plate a sunlit left third, so --muted copy
     over either measures only ~2.5-3.7:1 even in a DARK theme, not just in light
     ones. Copy blocks marked .plate-copy therefore take --ink in both themes (8.7:1
     or better). Scoped this way so the .muted inside the opaque plan cards keeps its
     intended hierarchy; the .band.has-photo and :root[data-theme] parts are carried
     only to outrank the per-theme rules above. */
  .band.has-photo .plate-copy .lead,
  .band.has-photo .plate-copy .muted,
  :root[data-theme="light"] .band.has-photo .plate-copy .lead,
  :root[data-theme="light"] .band.has-photo .plate-copy .muted,
  :root[data-theme="dark"] .band.has-photo .plate-copy .lead,
  :root[data-theme="dark"] .band.has-photo .plate-copy .muted { color: var(--ink); }
  .hero-grid { display: grid; grid-template-columns: 1.05fr .95fr; gap: 48px; align-items: center; position: relative; z-index: 1; }
  .hero h1 { margin-top: 18px; }
  .hero .lead { margin-top: 20px; }
  .hero .cta-row { margin-top: 28px; }
  .qualify { margin-top: 18px; font-family: var(--display); font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: var(--track-caps); color: var(--muted); }
  .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; }

  /* ---- phone-frame figures + hero callout chips ---- */
  /* The bezel is a hardware object: near-black in both themes, drawn with padding on
     the <figure> itself so no wrapper element is needed. */
  figure.phone { margin: 0; position: relative; width: min(300px, 80vw);
    padding: 10px; border-radius: 40px; background: #171B22;
    border: 1px solid rgba(238,241,246,.16);
    box-shadow: inset 0 1px 0 rgba(255,255,255,.07), var(--shadow); }
  figure.phone img { display: block; width: 100%; height: auto; border-radius: 30px;
    background: var(--surface); box-shadow: 0 0 0 1px rgba(0,0,0,.55); }
  figure.phone.hero-phone { margin: 0 auto; width: min(340px, 84vw); }
  figure.phone.answer-shot { margin: 40px auto 0; width: min(340px, 84vw); }
  @media (max-width: 480px) {
    figure.phone { padding: 8px; border-radius: 34px; }
    figure.phone img { border-radius: 26px; }
  }
  .spot-callout { position: absolute; font-family: var(--numeric);
    font-variant-numeric: tabular-nums; font-size: var(--fs-micro); font-weight: 700;
    font-variant-numeric: tabular-nums; white-space: nowrap; padding: 6px 10px; border-radius: var(--radius);
    background: var(--surface); border: 1px solid var(--line-strong); box-shadow: var(--shadow); color: var(--ink); }
  .spot-callout-1 { top: 78%; left: -22px; }
  .spot-callout-2 { top: 82%; right: -16px; color: var(--energy); }
  .spot-callout-3 { top: 85%; left: -30px; }

  /* ---- how-it-works step cards ---- */
  .card.step .step-shot { display: block; width: 100%; height: auto; border-radius: var(--radius-md);
    border: 1px solid var(--line); background: var(--surface-2); margin: 14px 0 16px; }

  /* ---- theme-swapped screenshots ---- */
  /* Dark is the default capture; light-theme users (OS preference, or the manual
     toggle overriding it) get a light-mode capture of the SAME screen instead, so
     the phone's own chrome always matches the site around it. Only screens with
     both captures wear these classes — the rest stay theme-agnostic.
     !important: .step-shot/figure.phone img base rules outrank a plain .shot-light/
     .shot-dark class on specificity alone (more element selectors), so without it
     both captures would render stacked instead of one replacing the other. */
  .shot-light { display: none !important; }
  @media (prefers-color-scheme: light) {
    .shot-dark { display: none !important; }
    .shot-light { display: block !important; }
  }
  :root[data-theme="light"] .shot-dark { display: none !important; }
  :root[data-theme="light"] .shot-light { display: block !important; }
  :root[data-theme="dark"] .shot-dark { display: block !important; }
  :root[data-theme="dark"] .shot-light { display: none !important; }

  /* ---- capability chapters ---- */
  .chapter { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 64px; align-items: center;
    padding: 64px 0; border-top: 1px solid var(--line); }
  .chapter:first-child { border-top: none; padding-top: 0; }
  .chapter:last-child { padding-bottom: 0; }
  .chapter h3 { font-size: clamp(2rem, 3.75vw, 2.75rem); line-height: 1.1; }
  .chapter figure.phone { justify-self: center; }
  .chapter.flip figure.phone { order: -1; }

  /* ---- cards / grids ---- */
  .grid { display: grid; gap: 20px; }
  .g3 { grid-template-columns: repeat(3, 1fr); }
  .g2 { grid-template-columns: repeat(2, 1fr); }
  /* border-top after `border` so it overrides only the top edge. The 2px
     accent rule is the app SectionCard's signature, echoed here. */
  .card { background: var(--surface); border: 1px solid var(--line);
    border-top: 2px solid color-mix(in srgb, var(--accent) 40%, transparent);
    border-radius: var(--radius-lg); padding: 26px; }
  .card h3 { margin-bottom: 8px; }
  .card p { color: var(--muted); font-size: var(--fs-body); font-family: var(--sans); }
  .num { font-family: var(--display); font-size: var(--fs-label); color: var(--accent); font-weight: 700; letter-spacing: var(--track-caps); }
  /* ---- engineer sheet: the same language the app now speaks ----
     A finding reads top-down — what it is, what it means, why — with the number
     right-aligned in tabular figures so magnitudes stack down the card. The glyph
     rail is the scannable index: every mark is a fragment of a racing line plus a
     marker (identical geometry to the app's CustomPainter), drawn in currentColor
     so it inherits its row's colour and owns no palette of its own. */
  .lockup { display: flex; align-items: center; gap: 9px; padding-bottom: 18px; }
  .lockup-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--energy); flex: none; }
  .lockup-word { font-family: var(--display); font-size: 13px; font-weight: 800; letter-spacing: .2em; color: var(--ink); }
  .lockup-word + .lockup-word { margin-left: .45em; }
  .lockup-rule { flex: 1; height: 1px; background: var(--line); }
  .sheet-stats { display: flex; gap: 26px; flex-wrap: wrap; padding-bottom: 18px; border-bottom: 1px solid var(--line); }
  .sheet-stats > div { display: flex; flex-direction: column; gap: 3px; }
  .sheet-stat-label { font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
  .sheet-stat-val { font-size: 21px; font-weight: 600; font-variant-numeric: tabular-nums; color: var(--ink); line-height: 1.1; }
  .sheet-rows { list-style: none; margin: 0; padding: 0; }
  .sheet-rows li { display: grid; grid-template-columns: auto 1fr auto; gap: 0 14px;
    align-items: start; padding: 16px 0; border-bottom: 1px solid var(--line); }
  .sheet-rows li:last-child { border-bottom: none; padding-bottom: 0; }
  .glyph { fill: none; stroke: currentColor; stroke-width: 2.1; stroke-linecap: round;
    stroke-linejoin: round; color: var(--muted); display: block; margin-top: 1px; }
  .is-focus .glyph { color: var(--bad); }
  .sheet-body { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
  .sheet-eyebrow { font-size: 10.5px; font-weight: 700; letter-spacing: .14em;
    text-transform: uppercase; color: var(--muted); }
  .is-focus .sheet-eyebrow { color: var(--bad); }
  .sheet-title { font-family: var(--sans); font-size: 16px; font-weight: 600; line-height: 1.2; color: var(--ink); }
  .is-focus .sheet-title { font-size: 18px; }
  .sheet-why { font-family: var(--sans); font-size: 13.5px; line-height: 1.45; color: var(--muted); }
  .sheet-metric { font-size: 18px; font-weight: 700; text-align: right; font-variant-numeric: tabular-nums; line-height: 1.15; }
  .is-focus .sheet-metric { font-size: 26px; }
  /* The one positive finding carries the good colour rather than sitting grey with
     the losses — a strength you can see at a glance is the point of ranking at all.
     Its own class, not the .is-good utility, so the colour lands on the glyph,
     eyebrow and title and does NOT inherit into the muted why-line. */
  .is-strength .glyph,
  .is-strength .sheet-eyebrow,
  .is-strength .sheet-title { color: var(--good); }
  .is-accent { color: var(--accent); } .is-good { color: var(--good); } .is-bad { color: var(--bad); }
  @media (max-width: 620px) {
    .sheet-rows li { grid-template-columns: auto 1fr; }
    .sheet-metric { grid-column: 2; text-align: left; margin-top: 2px; }
  }
  .flist { list-style: none; margin: 0; padding: 0; }
  .flist li { padding: 12px 0; border-top: 1px solid var(--line); font-size: var(--fs-body); display: flex; gap: 12px; align-items: baseline; font-family: var(--sans); }
  .flist li::before { content: "→"; color: var(--accent); font-family: var(--display); font-weight: 700; }

  /* devices */
  .devtiles { display: grid; grid-template-columns: repeat(4,1fr); gap: 16px; }
  .devtile { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 22px; text-align: center; }
  .devtile .ring { width: 54px; height: 54px; border-radius: 50%; margin: 0 auto 12px; display: flex; align-items: center; justify-content: center;
    border: 3px solid var(--ring-color, var(--accent)); background: var(--surface-2);
    font-family: var(--display); font-weight: 700; font-size: var(--fs-micro); letter-spacing: .02em; color: var(--ink); }
  .devtile h3 { font-weight: 700; font-size: var(--fs-base); letter-spacing: -.01em; font-family: var(--sans); }
  .devtile .devline { margin-top: 10px; font-size: var(--fs-body); line-height: 1.55; color: var(--muted); font-family: var(--sans); }

  /* comparison table */
  .tablewrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius-lg); }
  table { border-collapse: collapse; width: 100%; min-width: 620px; font-size: var(--fs-body); background: var(--surface); }
  th, td { text-align: left; padding: 14px 16px; border-bottom: 1px solid var(--line); }
  thead th { font-family: var(--display); font-size: var(--fs-micro); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--muted); font-weight: 700; background: var(--surface-2); }
  tbody th { color: var(--muted); font-family: var(--display); font-weight: 400; font-size: var(--fs-label); letter-spacing: .01em; }
  tbody td { font-family: var(--mono); }
  .yes { color: var(--good); font-weight: 700; } .no { color: var(--bad); } .part { color: var(--warn); }
  /* Colours a fragment of a heading. Blue already carries non-interactive meaning
     here (every .eyebrow uses it), and the two-tone split echoes the logo's own
     LAPPER/SNAPPER break, so the accent lands on the punchline rather than the
     whole line — a full heading in accent reads like an oversized link. */
  .accent { color: var(--accent); }
  .col-us { background: color-mix(in srgb, var(--accent) 9%, transparent); }

  /* pricing */
  .plans { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 22px; align-items: start; }
  .plan { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 30px; position: relative; }
  .plan.pro { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), var(--shadow); }
  .plan .badge { position: absolute; top: -12px; right: 22px; background: var(--accent); color: var(--accent-ink);
    font-family: var(--display); font-size: var(--fs-micro); letter-spacing: var(--track-caps); text-transform: uppercase; padding: 5px 10px; border-radius: var(--radius); font-weight: 700; }
  .price { font-family: var(--numeric); font-variant-numeric: tabular-nums; font-size: 2.6rem; font-weight: 700; letter-spacing: -.02em; }
  .price small { font-size: .9rem; color: var(--muted); font-weight: 600; letter-spacing: 0; font-family: var(--display); text-transform: uppercase; }
  .toggle { display: inline-flex; border: 1px solid var(--line); border-radius: var(--radius); padding: 4px; gap: 4px; font-family: var(--display); }
  .toggle button { background: none; border: none; color: var(--muted); font-family: var(--display); font-size: var(--fs-micro);
    text-transform: uppercase; letter-spacing: var(--track-caps); padding: 7px 13px; border-radius: var(--radius); cursor: pointer; font-weight: 600; }
  .toggle button.on { background: var(--accent); color: var(--accent-ink); font-weight: 700; }
  .plan ul { list-style: none; padding: 0; margin: 20px 0; }
  /* Not flex: a flex row would split each text run and mid-sentence <strong> into
     separate flex items, fragmenting the sentence into stacked columns on phones.
     The glyph hangs in the padding instead and the sentence flows naturally. */
  .plan li { padding: 9px 0 9px 26px; border-top: 1px solid var(--line); font-size: var(--fs-body); font-family: var(--sans); position: relative; }
  .plan li::before { content: "✓"; color: var(--good); font-weight: 700; position: absolute; left: 0; top: 9px; }
  .plan li.na { color: var(--muted); } .plan li.na::before { content: "—"; color: var(--muted); }
  /* Plan CTAs carry the longest strings on the site (localized trial copy); .btn's
     white-space:nowrap would set the card's min-content wider than a phone. */
  .plan .btn { white-space: normal; text-align: center; text-wrap: balance; }
  .teamrow { margin-top: 22px; background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 20px 24px;
    display: flex; justify-content: space-between; align-items: center; gap: 16px; flex-wrap: wrap; }

  /* prose (legal / about) */
  .prose { max-width: 68ch; }
  .prose h2 { font-size: 1.5rem; margin-top: 40px; }
  .prose h3 { margin-top: 28px; margin-bottom: 6px; }
  .prose p, .prose li { color: var(--ink); font-size: var(--fs-body); font-family: var(--sans); }
  .prose .muted, .prose small { color: var(--muted); }
  .prose ul { padding-left: 20px; }
  .callout { background: var(--surface-2); border-left: 3px solid var(--accent); border-radius: var(--radius); padding: 14px 18px; font-size: var(--fs-body); font-family: var(--sans); }

  /* faq */
  .faq details { border-top: 1px solid var(--line); padding: 6px 0; }
  .faq summary { cursor: pointer; padding: 14px 0; font-weight: 600; list-style: none; display: flex; justify-content: space-between; font-family: var(--sans); }
  .faq summary::-webkit-details-marker { display: none; }
  .faq summary::after { content: "+"; color: var(--accent); font-family: var(--display); }
  .faq details[open] summary::after { content: "\2013"; }
  .faq p { color: var(--muted); padding-bottom: 14px; font-size: var(--fs-body); font-family: var(--sans); }

  /* form */
  .field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
  .field label { font-family: var(--display); font-size: var(--fs-micro); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--muted); }
  .field input, .field select, .field textarea {
    background: var(--surface-2); border: 1px solid var(--line-strong); border-radius: var(--radius); padding: 12px 14px;
    color: var(--ink); font-family: var(--sans); font-size: var(--fs-body);
  }

  /* footer */
  footer { border-top: 1px solid var(--line); background: var(--surface); margin-top: 20px; }
  .foot { display: grid; grid-template-columns: 1.4fr 1fr 1fr 1fr; gap: 32px; padding: 56px 0 32px; }
  .foot .foot-label { font-family: var(--display); font-size: var(--fs-micro); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--muted); margin: 0 0 14px; font-weight: 700; }
  .foot a { display: block; text-decoration: none; color: var(--ink); font-size: var(--fs-body); padding: 5px 0; cursor: pointer; font-family: var(--sans); }
  .foot a:hover { color: var(--accent); }
  .footbar { border-top: 1px solid var(--line); padding: 20px 0; display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap;
    font-size: var(--fs-label); color: var(--muted); font-family: var(--mono); }
  .disclaimer { font-size: var(--fs-micro); color: var(--muted); max-width: 70ch; line-height: 1.5; padding: 0 0 28px; font-family: var(--sans); }
  /* .foot and .footbar each set their own padding shorthand, which zeroes the
     left/right gutter .wrap would otherwise add — this row matches that so the
     disclaimer lines up with the store badges and copyright bar above/below it. */
  .foot-disclaimer { padding-left: 0; padding-right: 0; }

  /* Injected by the build into Privacy and Terms on non-English pages only. */
  .english-only-notice { font-size: var(--fs-label); color: var(--muted); border: 1px solid var(--line);
    border-radius: var(--radius); padding: 10px 12px; margin: 0 0 24px; }

  /* Suggests a locale; never redirects. Hidden until JS decides to show it. */
  .i18n-banner { display: flex; gap: 12px; align-items: center; justify-content: center;
    padding: 10px 16px; font-size: var(--fs-label); background: var(--line); }
  .i18n-banner[hidden] { display: none; }
  .i18n-banner a { color: var(--accent); font-weight: 700; }
  /* --ink, not --muted: against the banner tint (--line over --bg) muted was
     the marginal case that first exposed the light palette's contrast problem.
     The token itself is now darker (#4B5364, 5.85:1 on --bg), so this is no
     longer load-bearing — but --ink is still the right call for a control. */
  .i18n-banner button { background: none; border: 0; color: var(--ink); cursor: pointer; font: inherit; }

  .section-head { max-width: 60ch; }
  .section-head .lead { margin-top: 12px; }
  .mt-lg { margin-top: 40px; }
  .mt-md { margin-top: 24px; }

  /* Moment band: copy left, promo film right. The video ships poster-only
     (preload="none") so the band costs one 35 KB JPEG until someone presses play. */
  .moment-grid { display: grid; grid-template-columns: 1fr 1.05fr; gap: 48px; align-items: center; }
  .promo-video { margin: 0; }
  .promo-video video { display: block; width: 100%; height: auto; border: 1px solid var(--line-strong); background: #12151A; }

  @media (max-width: 900px) {
    /* Narrow screens stack the grid, so the copy sits alone under a band of empty
       sky — the same top padding reads as a bigger gap here than it does on desktop. */
    .hero { padding-top: 40px; }
    .hero-grid { grid-template-columns: 1fr; gap: 32px; }
    .moment-grid { grid-template-columns: 1fr; gap: 32px; }
    /* minmax(0,1fr), not 1fr: a grid item's min-width:auto would let one long
       no-wrap child (the Pro trial CTA, localized) widen the track past the
       viewport and force page-level horizontal scroll. */
    .g3, .g2, .plans, .devtiles { grid-template-columns: minmax(0, 1fr); }
    .devtiles { grid-template-columns: 1fr 1fr; }
    .foot { grid-template-columns: 1fr 1fr; }
    .nav-links { display: none; }
    .menu-btn { display: inline-flex; }
    header .nav > a.btn-primary { display: none; }
    section.band { padding: 60px 0; }
    section.band.elevated { padding: 76px 0; }
    .nav-links.open { display: flex; position: absolute; top: 96px; left: 0; right: 0; flex-direction: column;
      background: var(--bg); border-bottom: 1px solid var(--line); padding: 16px 24px; gap: 4px; }
    .nav-links.open a { padding: 12px 0; font-size: var(--fs-body); }
    .nav-links.open a.mobile-cta { display: block; font-weight: 700; }
    .chapter { grid-template-columns: 1fr; gap: 28px; padding: 40px 0; }
    /* Stacked chapters lead with the screen, then the copy — flip included. */
    .chapter figure.phone, .chapter.flip figure.phone { order: -1; }
    .spot-callout-1 { left: -8px; }
    .spot-callout-2 { right: -6px; }
    .spot-callout-3 { left: -12px; }
  }

  /* The brand lockup (337px at its 44px desktop height) forces horizontal overflow on
     narrow phones. Below 480px it turns fluid: the controls keep their tap size and the
     logo absorbs whatever width remains. Header-scoped — the footer logo keeps its own
     fixed 30px sizing and must not letterbox inside it. The vertical padding keeps the
     shrunken lockup a ≥24px-tall tap target. */
  @media (max-width: 480px) {
    .nav { gap: 12px; }
    .nav > .icon-btn, .langswitch { flex: none; }
    header .brand { flex: 1 1 auto; min-width: 0; padding: 10px 0; }
    .brand { min-width: 0; }
    header .brand .logo { width: 100%; max-width: 337px; height: auto; }
    /* Long localized CTAs ("Empieza la prueba gratuita de 14 días — sin tarjeta")
       must wrap on phones instead of forcing page-level horizontal scroll. */
    .btn { white-space: normal; text-align: center; }
  }

  /* ---- store badges ---- */
  /* Anchor targets scrolled to from a same-document link sit under the 96px sticky header. */
  #get, #how, #chapters { scroll-margin-top: 112px; }
  .store-badges { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
  a.store-badge { display: inline-flex; padding: 0; border-radius: var(--radius); line-height: 0; }
  a.store-badge:hover { opacity: .85; }
  a.store-badge svg { display: block; }

  /* ---- motion: scroll reveals + the hero callout showpiece ----
     Every pre-hidden state is double-gated: the boot script stamps .js on <html>, and
     the whole block sits inside prefers-reduced-motion: no-preference. Without JS, or
     with reduced motion, nothing is ever hidden and nothing moves. */
  @media (prefers-reduced-motion: no-preference) {
    .js .reveal { transition: opacity .6s ease, transform .6s ease; }
    .js .reveal:not(.in) { opacity: 0; transform: translateY(18px); }
    /* Sibling cards in a grid (the how-it-works steps) ripple in left to right. */
    .js .grid > .reveal:nth-child(2) { transition-delay: .12s; }
    .js .grid > .reveal:nth-child(3) { transition-delay: .24s; }

    /* The one set piece: the hero's corner-loss chips land on the screenshot shortly
       after load, 120ms apart; the orange delta chip gets a brief overshoot pop. */
    .js .hero-phone .spot-callout { opacity: 0; animation: chip-in .55s cubic-bezier(.2,.7,.3,1) forwards; }
    .js .hero-phone .spot-callout-1 { animation-delay: .40s; }
    .js .hero-phone .spot-callout-2 { animation-name: chip-in-pop; animation-duration: .7s; animation-delay: .52s; }
    .js .hero-phone .spot-callout-3 { animation-delay: .64s; }
  }
  @keyframes chip-in {
    from { opacity: 0; transform: translateY(12px); }
    to { opacity: 1; transform: none; }
  }
  @keyframes chip-in-pop {
    0% { opacity: 0; transform: translateY(12px) scale(.92); }
    72% { opacity: 1; transform: translateY(-2px) scale(1.07); }
    100% { opacity: 1; transform: none; }
  }
