/*
Theme Name: Aimllama
Description: The bubbly theme, on the web — same tokens as the app (shared/design-tokens) and the Windows GUI.
Version: 0.2
*/

/* ── tokens ───────────────────────────────────────────────────────────────────
   The hues are the app's, unchanged. What is added here is the half of the ramp
   the web copy never carried (surface/elevated/inset/border hexes, the pressed
   state) plus a radius and type scale, so the split-corner rule below is one
   variable rather than a judgement call at every rule.

   `--ink` stays #FFFFFF even though tokens.json says #FFFAFB: DESIGN.md records
   that as known, unresolved drift and an owner call, not a silent edit. ─────── */
:root {
  --violet: #339989; --violet-hover: #57B8A8; --violet-pressed: #2A7A6E;
  --sky: #7DE2D1; --mint: #7DE2D1; --pink: #7DE2D1;
  --bg: #161817; --bg-top: #222423; --bg-inset: #131515;
  --bg-surface: #232524; --bg-elevated: #2B2C28;
  --surface: rgba(255,255,255,.06); --edge: rgba(255,255,255,.09);
  --border-subtle: #333634; --border-strong: #4A4E4B;
  --ink: #FFFFFF; --ink-2: #CFD8D5; --muted: #8FA39E;
  --warning: #F5A524;
  --sans: system-ui, "Segoe UI", Roboto, sans-serif;

  /* SPLIT CORNERS. The references are hard-edged; our two apps are not, and the
     site has to keep looking like the product it sells. So chrome — the hero
     frame, the nav box, actions, section rules — goes angular, and anything
     that mirrors an app surface keeps the app's radius. */
  --r-chrome: 4px;   /* hero framing, buttons, section edges */
  --r-card: 22px;    /* == theme.rs RADIUS_CARD; cards, tiles, glass */
  --r-tile: 16px;
  --r-pill: 999px;

  /* The real rendered height of the fixed bar, measured — it read 76 while the
     bar was 97, so every "clear the bar" offset was quietly 21px short. */
  --bar-h: 96px;
  --shell: 1120px;
}
* { box-sizing: border-box; }

/* `clip`, not `hidden`: they stop the same sideways scroll, but `overflow:hidden`
   on an ancestor silently kills `position: sticky` on every descendant — which
   is what used to make a sticky bar impossible here. */
html { background: var(--bg); overflow-x: clip; }
body {
  margin: 0; color: var(--ink-2); font: 16px/1.6 var(--sans);
  background: linear-gradient(180deg, var(--bg-top), var(--bg));
  min-height: 100vh; position: relative;
}
/* the blobs — the same fractions the app draws (theme::backdrop, Compose Blobs) */
body::before, body::after { content: ""; position: fixed; z-index: -1; border-radius: 50%; filter: blur(60px); pointer-events: none; }
body::before { width: 520px; height: 520px; right: -160px; top: -160px; background: rgba(51,153,137,.45); }
body::after { width: 460px; height: 460px; left: -180px; bottom: -120px; background: rgba(125,226,209,.22); }
a { color: var(--sky); }
p { margin: 0 0 1em; }

/* ── type ─────────────────────────────────────────────────────────────────────
   Still system-ui: DESIGN.md pins the web stack and the repo ships no webfont,
   so the display feel comes from case, weight and tracking instead — which cost
   nothing and cannot shift the layout while a font loads. Note h1 tracking went
   from NEGATIVE to positive; wide-set uppercase is most of what makes the
   reference sites read as display type. ─────────────────────────────────────── */
h1, h2, h3 { color: var(--ink); line-height: 1.08; margin: 0 0 .4em; }
h1 { font-size: clamp(38px, 7vw, 76px); font-weight: 800; letter-spacing: .01em; text-transform: uppercase; }
h2 { font-size: clamp(24px, 3.4vw, 38px); font-weight: 800; letter-spacing: .01em; text-transform: uppercase; }
h3 { font-size: 16px; font-weight: 700; letter-spacing: .01em; }
.eyebrow {
  display: block; color: var(--mint); font-size: 12px; font-weight: 800;
  letter-spacing: .18em; text-transform: uppercase; margin: 0 0 14px;
}

.wrap { max-width: var(--shell); margin: 0 auto; padding: 0 24px; }

/* ── the bar ──────────────────────────────────────────────────────────────────
   A boxed bar floating over the hero, which is the shape the references use and
   the shape this bar already had — it just sat in the document flow. Fixed now,
   and it condenses on scroll (see the motion section). */
.bar {
  position: fixed; inset: 0 0 auto; z-index: 40;
  padding: 14px 24px;
}
.bar .box {
  max-width: var(--shell); margin: 0 auto;
  display: flex; align-items: center; gap: 12px;
  /* 24px inner, so the wordmark sits on the same vertical as the h1 under it —
     `.wrap` pads by 24 and the bar was padding by 18. */
  padding: 10px 24px; border-radius: var(--r-chrome);
  border: 1px solid var(--edge); background: rgba(19,21,21,.72);
  backdrop-filter: blur(14px);
}
.bar .mark { width: 30px; height: 30px; flex: none; color: var(--mint); }
.bar .mark svg { width: 100%; height: 100%; display: block; }
.bar .brand { color: var(--ink); font-weight: 800; font-size: 17px; text-decoration: none; letter-spacing: .06em; text-transform: uppercase; }
.bar nav { margin-left: auto; display: flex; gap: 2px; }
/* 44px of target, and the active page is marked with a rule under it rather
   than a pill — the references underline, and it leaves the radius question to
   the one place that answers it. */
.bar nav a {
  position: relative; color: var(--ink-2); text-decoration: none;
  font-weight: 700; font-size: 13px; letter-spacing: .08em; text-transform: uppercase;
  padding: 13px 14px; border-radius: var(--r-chrome);
}
.bar nav a::after {
  content: ""; position: absolute; left: 14px; right: 14px; bottom: 8px; height: 2px;
  background: var(--mint); transform: scaleX(0); transform-origin: 0 50%;
  transition: transform .18s ease;
}
.bar nav a:hover { color: var(--ink); }
.bar nav a:hover::after, .bar nav a[aria-current]::after { transform: scaleX(1); }
.bar nav a[aria-current] { color: var(--ink); }

/* ── hero ─────────────────────────────────────────────────────────────────────
   Full-bleed, and it has to break out of the shell: the bar and footer used to
   sit inside one 1040px `.wrap` that wrapped the whole document, so nothing
   could ever run edge to edge. Each section owns its own `.wrap` now. */
.hero {
  position: relative;
  /* NOT `hidden`. `overflow: hidden` makes this a scroll container, and a
     `view()` timeline resolves against the nearest ancestor SCROLLER — so the
     backdrop parallax and the scroll cue were timed against a box that never
     scrolls, and neither ever ran. `clip` clips the same and creates no
     scroller. */
  overflow: clip;
  /* Exactly the first screen. `svh` not `vh`: mobile browser chrome makes
     `100vh` taller than what you can actually see. */
  min-height: 100svh;
  display: flex; align-items: center;
  padding: calc(var(--bar-h) + 28px) 0 64px;
  border-bottom: 1px solid var(--edge);
}
.hero-bg { position: absolute; inset: -10% 0; z-index: 0; pointer-events: none; }
/* Built in CSS, not a stock photo: a teal wash, and a faint grid that gives the
   band something to parallax against. */
.hero-bg::before {
  content: ""; position: absolute; inset: 0;
  background:
    radial-gradient(58% 70% at 72% 26%, rgba(51,153,137,.55), transparent 68%),
    radial-gradient(52% 55% at 8% 88%, rgba(125,226,209,.24), transparent 70%),
    radial-gradient(90% 60% at 50% 120%, rgba(19,21,21,.85), transparent 60%);
}
.hero-bg::after {
  content: ""; position: absolute; inset: 0;
  background-image:
    linear-gradient(to right, rgba(255,255,255,.10) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(255,255,255,.10) 1px, transparent 1px);
  background-size: 64px 64px;
  mask-image: radial-gradient(70% 65% at 60% 35%, #000, transparent 78%);
}
.hero .wrap { position: relative; z-index: 1; }
/* Copy left, the product right — the references fill this half with a bought
   photograph; ours is the app, which is the only image on this site that is
   actually about the thing being sold. */
.hero-grid { display: grid; gap: 40px; grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr); align-items: center; }
.hero-art { position: relative; justify-self: center; }
.hero-art::before {
  content: ""; position: absolute; inset: -14% -22%;
  background: radial-gradient(50% 50% at 50% 50%, rgba(51,153,137,.55), transparent 70%);
  filter: blur(10px);
}
.hero-art img {
  position: relative; display: block; width: 100%; max-width: 268px; height: auto;
  border-radius: 28px; border: 1px solid var(--border-strong);
  box-shadow: 0 40px 80px -30px #000, 0 0 0 8px rgba(255,255,255,.03);
  transform: rotate(-3deg);
}
/* The display line is the SHORT one — the references carry two lines, not five.
   The sentence that explains is the lead's job. */
/* Each line is its own block so the stagger has something to stagger. */
.hero h1 { max-width: 14ch; }
.hero h1 .l { display: block; }
.hero .lead { font-size: 18px; color: var(--ink-2); max-width: 48ch; }
.hero .pill {
  display: inline-flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 700;
  color: var(--ink); background: var(--surface); border: 1px solid var(--edge);
  border-radius: var(--r-pill); padding: 7px 14px; margin-bottom: 14px;
}
.hero .actions { display: flex; flex-wrap: wrap; gap: 12px; margin: 26px 0 0; }
.hero-note { margin: 18px 0 0; font-size: 12px; color: var(--muted); letter-spacing: .04em; }

/* the scroll cue — a DRAWN arrow. No icon on this site is ever a character. */
.scroll-cue {
  position: absolute; left: 50%; bottom: 26px; transform: translateX(-50%);
  z-index: 1; display: grid; justify-items: center; gap: 8px;
  color: var(--ink-2); font-size: 10px; font-weight: 800; letter-spacing: .2em; text-transform: uppercase;
}
.scroll-cue svg { width: 16px; height: 22px; stroke: currentColor; fill: none; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
/* The bounce is a PLAIN animation, deliberately outside @supports: it needs no
   scroll timeline, so Firefox gets it too. It is also the only infinite motion
   on the site, which is why it is small. */
.scroll-cue svg { animation: cue-bounce 1.5s cubic-bezier(.5,.05,.5,.95) infinite; }
/* A real bounce, not a hover: falls, rebounds short, falls again, settles.
   A single ease-in-out sine reads as drifting rather than bouncing. */
@keyframes cue-bounce {
  0%   { transform: translateY(0); }
  40%  { transform: translateY(13px); }
  55%  { transform: translateY(5px); }
  70%  { transform: translateY(12px); }
  85%  { transform: translateY(1px); }
  100% { transform: translateY(0); }
}

/* ── bands ────────────────────────────────────────────────────────────────── */
.band { padding: 76px 0; border-bottom: 1px solid var(--edge); }
.band:last-of-type { border-bottom: 0; }
.band-head { max-width: 68ch; margin: 0 0 34px; }
.section-lead { color: var(--muted); font-size: 15px; max-width: 64ch; margin: 0; }

/* ── cards + tiles — ROUND. These mirror app surfaces, so they keep the app's
   radius even while the chrome around them squares off. ───────────────────── */
.card { background: var(--surface); border: 1px solid var(--edge); border-radius: var(--r-card); padding: 24px; backdrop-filter: blur(8px); }
.grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }
.tile { position: relative; background: var(--surface); border: 1px solid var(--edge); border-radius: var(--r-card); padding: 22px; overflow: hidden; }
/* The reference cards carry a hard accent rule across the top — the one place
   the angular language touches a round card. */
.tile::before { content: ""; position: absolute; inset: 0 0 auto; height: 2px; background: linear-gradient(90deg, var(--violet), var(--mint)); opacity: .55; }
.tile .ic { width: 44px; height: 44px; border-radius: var(--r-tile); display: grid; place-items: center; background: rgba(51,153,137,.22); border: 1px solid var(--violet-hover); margin-bottom: 14px; }
.tile .ic svg { width: 22px; height: 22px; stroke: var(--mint); fill: none; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.tile p { color: var(--muted); font-size: 14px; margin: 0; }
.stats { display: flex; gap: 12px; flex-wrap: wrap; }
.stat { flex: 1; min-width: 140px; text-align: center; background: var(--surface); border: 1px solid var(--edge); border-radius: var(--r-tile); padding: 22px 12px; }
.stat b { display: block; font-size: clamp(30px, 5vw, 46px); font-weight: 800; color: var(--mint); line-height: 1; margin-bottom: 8px; }
.stat span { font-size: 12px; color: var(--muted); }

/* ── buttons + form — SHARP. The action is chrome, and a square action is most
   of what makes the references read the way they do. Pills stay pills. ────── */
/* Dark ink on the gradient, not white. White on the mint end (#7DE2D1) is
   about 1.9:1 — it fails at any size, and these are much bigger now than the
   18px pills this rule was written for. Against the same gradient dark ink is
   6.4:1 at the violet end and 14:1 at the mint. Worth knowing: the two APPS
   still draw white on this gradient and have the same problem. */
.aim-btn, .btn {
  display: inline-block; text-decoration: none; color: var(--bg-inset);
  font-weight: 800; font-size: 14px; letter-spacing: .09em; text-transform: uppercase;
  padding: 17px 30px; border-radius: var(--r-chrome); border: 0; cursor: pointer;
  background: linear-gradient(120deg, var(--violet), var(--pink));
  box-shadow: 0 12px 28px -8px var(--violet);
  transition: transform .16s ease, box-shadow .16s ease, background-color .16s ease;
}
.aim-btn:hover, .btn:hover { transform: translateY(-2px); box-shadow: 0 18px 34px -10px var(--violet); }
.aim-btn:active, .btn:active { transform: translateY(0); }
/* Only the primary action carries the gradient. DESIGN.md allows one gradient
   spot per surface and the front page had four; the other three are flat now. */
.btn.ghost { background: var(--surface); border: 1px solid var(--border-strong); box-shadow: none; color: var(--ink); }
.btn.ghost:hover { background: rgba(255,255,255,.1); box-shadow: none; }
.aim-form { display: grid; gap: 12px; max-width: 440px; }
.aim-form input[type=email] { font: inherit; padding: 15px 18px; border-radius: var(--r-chrome); border: 1px solid var(--edge); background: var(--bg-inset); color: var(--ink); }
.aim-form input:focus { outline: 2px solid var(--violet-hover); }
.aim-note { font-size: 13px; color: var(--muted); }
/* Errors get their own class. `.aim-note` was doing reassurance, footnotes,
   cookie copy AND failures, which meant the failure looked like a footnote. */
.aim-error { font-size: 13px; font-weight: 600; color: var(--warning); }

/* ── long-form pages ──────────────────────────────────────────────────────── */
.page-head { padding: calc(var(--bar-h) + 36px) 0 40px; border-bottom: 1px solid var(--edge); position: relative; overflow: hidden; }
.page-head::before {
  content: ""; position: absolute; inset: 0; z-index: 0;
  background: radial-gradient(50% 120% at 20% 0%, rgba(51,153,137,.28), transparent 70%);
}
.page-head .wrap { position: relative; z-index: 1; }
/* `main.page`, NOT `.page` — WordPress's own `body_class()` puts a `page`
   class on <body>, so a bare `.page` rule silently padded the BODY of every
   page on the site by 48px top and 80px bottom. That is what stopped the
   100svh hero from starting at the top of the screen. Generic class names
   collide with WordPress's: keep the element in the selector. */
main.page { padding: 48px 0 80px; }
main.page .entry { max-width: 74ch; }
.entry h2 { margin-top: 1.8em; font-size: clamp(20px, 2.6vw, 28px); }
.entry h2:first-child { margin-top: 0; }
.entry ul, .entry ol { padding-left: 20px; }
.entry strong { color: var(--ink); }

footer { padding: 46px 0 56px; font-size: 12px; color: var(--muted); border-top: 1px solid var(--edge); margin-top: 0; }
footer a { color: var(--muted); }

/* Sticky mobile CTA — phones only; the download page hides it (it IS the CTA). */
.sticky-cta { display: none; }
@media (max-width: 640px) {
  .sticky-cta {
    display: block; position: fixed; left: 16px; right: 16px; bottom: 14px; z-index: 50;
    text-align: center; padding: 15px 0; border-radius: var(--r-pill);
    color: var(--ink); font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
    font-size: 13px; text-decoration: none;
    background: var(--bg-elevated); border: 1px solid var(--violet-hover);
    box-shadow: 0 10px 26px -8px rgba(0,0,0,.6);
  }
  body { padding-bottom: 74px; } /* content never hides under the pill */
}

/* FAQ — native <details>, no JS. */
.faq details { border: 1px solid var(--edge); border-radius: var(--r-tile); padding: 16px 20px; margin: 10px 0; background: var(--surface); }
/* 44px floor. The summary was 38 — DESIGN.md's rule is "everywhere, no
   exceptions", and a disclosure row is a tap target like any other. Padding
   rather than display:flex, which strips the disclosure marker. */
/* 44px floor. Padding AND a min-height: the padding is what actually gets it
   there, the min-height is the guarantee if the font metrics change. */
.faq summary { cursor: pointer; font-weight: 700; color: var(--ink); padding: 12px 0; min-height: 44px; display: flex; align-items: center; }
/* display:flex on a summary hides the disclosure marker, so draw it back. */
.faq summary::before {
  content: ""; flex: none; width: 8px; height: 8px; margin-right: 12px;
  border-right: 2px solid var(--mint); border-bottom: 2px solid var(--mint);
  transform: rotate(-45deg); transition: transform .2s ease;
}
.faq details[open] summary::before { transform: rotate(45deg); }
.faq summary::-webkit-details-marker { display: none; }
.faq details p { margin: 10px 0 2px; color: var(--ink-2); }

/* ── keyboard users: every interactive thing must show where focus is ─────── */
a:focus-visible, button:focus-visible, summary:focus-visible, input:focus-visible {
  outline: 2px solid var(--mint); outline-offset: 3px; border-radius: 6px;
}
/* A [hidden] element with a display rule of its own would still show. */
[hidden] { display: none !important; }

/* ── phone screenshots — a snap rail on a phone, a centred row on a desktop ─ */
.shots { display: flex; gap: 20px; overflow-x: auto; padding: 6px 2px 14px; scroll-snap-type: x mandatory; }
.shots figure { margin: 0; flex: 0 0 230px; scroll-snap-align: center; }
.shots img { width: 100%; height: auto; display: block; border-radius: var(--r-card); border: 1px solid var(--edge); background: var(--surface); }
.shots figcaption { font-size: 12px; color: var(--muted); text-align: center; padding-top: 12px; letter-spacing: .04em; }
@media (min-width: 900px) { .shots { justify-content: center; } }

/* ── games with a bundled layout ──────────────────────────────────────────── */
.game-grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); margin: 0 0 18px; }
.game { background: var(--surface); border: 1px solid var(--edge); border-radius: var(--r-card); padding: 20px 22px; }
.game h3 { font-size: 16px; margin-bottom: 10px; }
.game-counts { font-size: 13px; color: var(--ink-2); margin: 0 0 10px; }
.game-counts b { color: var(--mint); font-size: 17px; }
.game-counts span { color: var(--muted); margin: 0 4px; }
.game p:last-child { font-size: 13px; color: var(--muted); margin: 0; }

/* ── footer legal row ─────────────────────────────────────────────────────── */
footer .tagline { margin: 0 0 14px; max-width: 68ch; }
footer .legal { display: flex; flex-wrap: wrap; align-items: center; gap: 0 20px; margin: 0 0 8px; }
/* Same 44px floor: these were 19px tall, which is a text link pretending to
   be a target. The row gap drops to compensate so the footer does not sprawl. */
footer .legal a, footer .legal button {
  display: inline-flex; align-items: center; min-height: 44px;
  color: var(--ink-2); font: inherit; background: none; border: 0; padding: 0;
  cursor: pointer; text-decoration: underline; text-underline-offset: 3px;
}
footer .legal a:hover, footer .legal button:hover { color: var(--mint); }
footer .copy { margin: 0; opacity: .8; max-width: 78ch; }

/* ── cookie consent ───────────────────────────────────────────────────────── */
.cookie-bar {
  position: fixed; left: 16px; right: 16px; bottom: 16px; z-index: 60;
  max-width: 760px; margin: 0 auto; display: flex; flex-wrap: wrap;
  align-items: center; gap: 12px 18px; padding: 16px 18px;
  background: rgba(19,21,21,.94); backdrop-filter: blur(12px);
  border: 1px solid var(--edge); border-radius: var(--r-card);
  box-shadow: 0 24px 60px -24px #000;
}
.cookie-bar p { margin: 0; flex: 1 1 240px; font-size: 13px; color: var(--ink-2); }
.cookie-bar-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.cookie-bar .btn { padding: 14px 18px; font-size: 12px; }
/* Two floating pills stacked on one another is a trap — the CTA can wait. */
body:has(.cookie-bar:not([hidden])) .sticky-cta { display: none; }

/* cookie policy tables — scroll rather than squash on a phone */
.cookie-table { display: block; overflow-x: auto; width: 100%; border-collapse: collapse; margin: 12px 0 24px; font-size: 14px; }
.cookie-table th, .cookie-table td { text-align: left; padding: 10px 12px; border-bottom: 1px solid var(--edge); vertical-align: top; }
.cookie-table th { color: var(--ink); font-size: 11px; letter-spacing: .07em; text-transform: uppercase; }
.cookie-table code { color: var(--mint); font-size: 13px; }

/* ── the setup page's own blocks ──────────────────────────────────────────────
   These used to live in an inline <style> inside the page body. That body sits
   in the DATABASE, not on disk, and the seeder is create-only — so editing the
   file changes nothing on a site that is already seeded. Written at `.entry`
   specificity, these beat the embedded block on the sites that already exist,
   today, with no database work. The seed file is cleaned separately for fresh
   installs. ──────────────────────────────────────────────────────────────── */
.entry .step { margin: 40px 0; }
.entry .step h2 { display: flex; align-items: center; gap: 14px; }
.entry .step h2 .n {
  display: inline-grid; place-items: center; width: 36px; height: 36px; flex: none;
  border-radius: var(--r-chrome); background: var(--bg-elevated);
  border: 1px solid var(--violet-hover); color: var(--mint);
  font-size: 15px; font-weight: 800;
}
.entry .ref { background: var(--surface); border: 1px solid var(--edge); border-radius: var(--r-card); padding: 20px; margin-top: 14px; }
.entry .ref svg { display: block; width: 100%; max-width: 420px; height: auto; margin: 0 auto; }
.entry .ref figcaption { text-align: center; font-size: 12px; color: var(--muted); margin-top: 10px; }
.entry .adv { border-left: 2px solid var(--violet); padding-left: 20px; }

/* ── hover, and why it is not in the @supports block ─────────────────────────
   All of this is `transition`, which every browser has had for a decade. The
   scroll effects below need `animation-timeline`, which Firefox still does not
   ship — so this layer is what keeps the page feeling alive there. */
.tile, .game, .card, .stat {
  transition: transform .22s cubic-bezier(.2,.7,.3,1), border-color .22s ease, background-color .22s ease;
}
.tile:hover, .game:hover, .card:hover, .stat:hover {
  transform: translateY(-4px);
  border-color: var(--violet-hover);
  background-color: rgba(255,255,255,.09);
}
.shots figure { transition: transform .22s cubic-bezier(.2,.7,.3,1); }
.shots figure:hover { transform: translateY(-6px) scale(1.02); }
.faq details { transition: border-color .2s ease, background-color .2s ease; }
.faq details:hover { border-color: var(--violet-hover); background-color: rgba(255,255,255,.09); }
.tile .ic { transition: transform .22s cubic-bezier(.2,.7,.3,1), background-color .22s ease; }
.tile:hover .ic { transform: scale(1.08) rotate(-4deg); background-color: rgba(51,153,137,.34); }
.bar .mark { transition: transform .3s cubic-bezier(.2,.7,.3,1); }
.bar .box:hover .mark { transform: rotate(-8deg) scale(1.06); }

/* ── MOTION ───────────────────────────────────────────────────────────────────
   Every reveal lives inside @supports. That is the whole safety argument:
   Firefox has no `animation-timeline` yet, and a reveal written the usual way
   (opacity:0, animate to 1) leaves its content PERMANENTLY INVISIBLE there.
   `opacity: 0` appears nowhere in this file outside these blocks — the default
   state of every element IS its finished state, and motion is added on top only
   where the browser can actually run it.

   The hero's entrance is a plain, timeline-less animation on purpose: a
   `view()` timeline on content that is already on screen resolves straight to
   its end state, so it would never be seen. ──────────────────────────────── */

/* the scroll progress rule — decoration, so it simply does not exist without
   support rather than sitting there at scaleX(0) forever */
.progress { display: none; }

@supports (animation-timeline: view()) {
  /* Travel, scale AND a blur burn-off. The first cut moved 30px and faded,
     which is a flicker you can scroll straight past without noticing. */
  .reveal { animation: rise linear both; animation-timeline: view(); animation-range: entry 0% cover 45%; }
  @keyframes rise { from { opacity: 0; transform: translateY(70px) scale(.94); filter: blur(6px); } }

  /* Grid children arrive from ALTERNATE sides — what gives the reference pages
     their sense of a page assembling itself rather than fading in.
     ONLY once the grid is genuinely multi-column. Below that the cards are
     full width, so a sideways slide means nothing AND it pushed the page 33px
     wider than the viewport at every narrow width — measured, not guessed. */
  @media (min-width: 760px) {
    .grid > .reveal:nth-child(odd), .game-grid > .reveal:nth-child(odd), .stats > .reveal:nth-child(odd) {
      animation-name: rise-left;
    }
    .grid > .reveal:nth-child(even), .game-grid > .reveal:nth-child(even), .stats > .reveal:nth-child(even) {
      animation-name: rise-right;
    }
  }
  @keyframes rise-left  { from { opacity: 0; transform: translate(-48px, 46px) scale(.94); filter: blur(6px); } }
  @keyframes rise-right { from { opacity: 0; transform: translate( 48px, 46px) scale(.94); filter: blur(6px); } }

  /* Staggered by RANGE, not by delay: a delay shifts a scroll timeline rather
     than sequencing it, and grid children sit at the same height, so without
     an offset they would all fire together. */
  .grid > :nth-child(2), .game-grid > :nth-child(2), .stats > :nth-child(2) { animation-range: entry 6% cover 50%; }
  .grid > :nth-child(3), .game-grid > :nth-child(3), .stats > :nth-child(3) { animation-range: entry 12% cover 55%; }
  .game-grid > :nth-child(4) { animation-range: entry 18% cover 60%; }

  /* Screenshots scale down into place as they cross. They are inside
     `.shots`, which is `overflow-x: auto` and therefore its own scroller — so
     the REVEAL goes on a wrapper outside the rail, and this is only the
     per-image polish. */
  .shots img { animation: shot linear both; animation-timeline: view(); animation-range: entry 0% cover 60%; }
  @keyframes shot { from { transform: scale(1.14); } }

  /* ── the hero: the screen that has to earn the motion ──────────────────── */

  /* Entrance. A plain, timeline-less animation on purpose: a `view()` timeline
     on content already on screen resolves straight to its end state and is
     never seen. Line by line, then the lead, then the buttons. */
  .hero .eyebrow, .hero h1 .l, .hero .lead, .hero .actions, .hero-note, .hero-art {
    animation: enter .8s cubic-bezier(.2,.75,.3,1) both;
  }
  @keyframes enter { from { opacity: 0; transform: translateY(38px); filter: blur(8px); } }
  .hero h1 .l:nth-child(1) { animation-delay: .06s; }
  .hero h1 .l:nth-child(2) { animation-delay: .14s; }
  .hero h1 .l:nth-child(3) { animation-delay: .22s; }
  .hero h1 .l:nth-child(4) { animation-delay: .30s; }
  .hero .lead    { animation-delay: .38s; }
  .hero .actions { animation-delay: .46s; }
  .hero-note     { animation-delay: .54s; }
  .hero-art      { animation-delay: .30s; animation-duration: 1s; }

  /* The phone drifts and tilts across its own travel through the viewport. At
     rest it sits near the middle of the range, so it reads as upright and
     tilts as you move. */
  .hero-art { animation: tilt linear both; animation-timeline: view(); animation-range: cover; }
  @keyframes tilt {
    from { transform: translateY(46px) rotate(-6deg); }
    to   { transform: translateY(-46px) rotate(3deg); }
  }

  /* The backdrop drifts the OTHER way — the parallax that never ran. */
  .hero-bg { animation: drift linear both; animation-timeline: view(); animation-range: cover; }
  @keyframes drift { from { transform: translateY(-9%) scale(1.06); } to { transform: translateY(14%) scale(1.06); } }

  /* Scrolling away from the hero is a transition, not a cut. */
  .hero-grid { animation: hero-leave linear both; animation-timeline: scroll(root); animation-range: 0 92svh; }
  @keyframes hero-leave { to { opacity: 0; transform: translateY(-70px); } }

  /* The cue answers the moment you take its advice. Driven from the document
     scroll rather than its own visibility, so "a little scrolling" is exactly
     what fades it. */
  .scroll-cue { animation: fade linear both; animation-timeline: scroll(root); animation-range: 0 240px; }
  @keyframes fade { to { opacity: 0; } }

  /* progress rule under the bar */
  .progress {
    display: block; position: fixed; inset: 0 0 auto; height: 3px; z-index: 45;
    background: linear-gradient(90deg, var(--violet), var(--mint));
    transform-origin: 0 50%;
    animation: grow linear both; animation-timeline: scroll(root);
  }
  @keyframes grow { from { transform: scaleX(0); } to { transform: scaleX(1); } }

  /* the bar condenses over the first 120px */
  .bar { animation: condense linear both; animation-timeline: scroll(root); animation-range: 0 120px; }
  @keyframes condense { to { padding-top: 6px; padding-bottom: 6px; } }
  .bar .box { animation: solidify linear both; animation-timeline: scroll(root); animation-range: 0 120px; }
  @keyframes solidify { to { background-color: rgba(19,21,21,.96); border-color: var(--border-subtle); } }

  /* Interior pages were completely static — `page.php` carried no `.reveal` at
     all, so anyone who clicked Download looking for this saw nothing move. */
  .page-head h1 { animation: enter .8s cubic-bezier(.2,.75,.3,1) both; }
  .entry > * { animation: rise linear both; animation-timeline: view(); animation-range: entry 0% cover 40%; }
}

/* One switch turns all of it off. Nothing here hides content, so a reader who
   asks for less motion loses the movement and keeps the page. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
    scroll-behavior: auto !important;
  }
  .progress { display: none; }
}

/* ── narrow screens: the top bar must wrap, or it forces the whole page wider
   than the viewport and every section gets clipped on the right. ─────────── */
@media (max-width: 860px) {
  :root { --bar-h: 88px; }
  .band { padding: 56px 0; }
  .hero { min-height: 100svh; padding: calc(var(--bar-h) + 24px) 0 56px; }
  .hero-grid { grid-template-columns: 1fr; gap: 32px; }
  .hero-art img { max-width: 210px; transform: none; }
}
@media (max-width: 720px) {
  .bar .box { flex-wrap: wrap; gap: 10px; }
  .bar nav { gap: 0; }
  .bar nav a { padding: 12px 10px; font-size: 12px; letter-spacing: .05em; }
  .scroll-cue { display: none; }
}
/* The nav drops to its own row HERE, chosen rather than left to whenever the
   flex items happen to stop fitting — because `--bar-h` is what reserves space
   under a FIXED bar, and a bar that wraps at a width the reserve does not know
   about puts the first heading under it. One breakpoint owns both. */
@media (max-width: 640px) {
  :root { --bar-h: 118px; }
  .bar .box { padding: 10px 16px; }
  .bar nav { width: 100%; margin: 2px 0 0; justify-content: space-between; }
}
@media (max-width: 470px) {
  .wrap { padding: 0 18px; }
  .bar { padding: 12px 16px; }
  .aim-btn, .btn { padding: 16px 22px; font-size: 13px; }
}
