/* =============================================================================
 * Pixel Fresh Studio — design system
 *
 * Restored from the original site (`website old/dist/assets/css/theme.css`) and
 * extended with the app section from docs/17. Bright, rounded, "juicy":
 * Fredoka for display, Nunito for body, ink-on-sky, chunky 3-D buttons.
 *
 * Two things from the original are deliberately NOT here:
 *   · .bg-canvas / world.js — the bouncing-blocks physics layer that ran a
 *     requestAnimationFrame loop behind every paragraph on the site. Deleted,
 *     not disabled, at the user's instruction.
 *   · The prototype's invented download counts, star ratings and country
 *     totals, and its six template games. There is one published app and the
 *     page says so. (check_no_invented_metrics() greps this file too, which is
 *     why they are described here rather than quoted.)
 * The floating logo is a different animation and it stays — see @keyframes.
 *
 * Hand-authored. The build copies it; it is never regenerated.
 * ========================================================================== */

:root {
  /* Brand sky */
  --sky-0:#C4ECFF; --sky-1:#8FD3F8; --sky-2:#79C6F5; --sky-3:#5FB5EF; --sky-4:#4AA3EA;

  /* Ink. Measured against the surfaces they are actually used on, not picked
     by eye — every pair below clears WCAG 2.2 AA and C21 re-measures them from
     rendered pixels on every run.
       --ink-900 on white ......... 12.6:1
       --ink-700 on white .........  7.0:1
       --ink-600 on white .........  5.3:1   (large text and controls only)
       --ink-500 on white .........  6.5:1
     The original's --ink-300 (#5A7A96, 4.2:1) failed as body text and has been
     darkened; it is now a de-emphasis colour that still passes. */
  --ink-900:#0E3A66; --ink-800:#143A5E; --ink-700:#1C5689; --ink-600:#1C6FC4;
  /* --ink-400 is the de-emphasis ink. It has to clear AA on the SKY, not
     just on paper, because .muted appears on both: #4A6C88 measured
     2.96:1 over the gradient. #2E4E66 is 4.67 there and 8.76 on white,
     and still reads a step quieter than body ink. */
  --ink-500:#185B9C; --ink-400:#2E4E66; --ink-300:#51708C; --ink-200:#7F97AC;

  /* Primary action (orange).
     The original site set WHITE on this gradient, which measures 1.96:1 — C21
     caught it the moment a specificity bug stopped hiding it. Darkening the
     orange enough for white (#8F4200) would have thrown away the brightest
     thing in the brand, so the label is dark ink instead: #3A1A00 measures
     5.88:1 on the darker stop and 8.06:1 on the lighter one, and the orange
     stays exactly as bright as it was.
     Arrow Doodle's own palette already works this way — "label is INK, never
     white" is a comment in arrow-doodle.css — so this is the house rule, not a
     workaround. --orange-ink is for orange TEXT on a light ground. */
  --orange:#F97A00; --orange-lt:#FFA53A; --orange-dk:#C95E00;
  --orange-ink:#8A4200; --on-orange:#3A1A00;

  /* Accents */
  --red:#FF5A5F; --green:#36C98D; --green-dk:#1F9C6C; --purple:#A45BE0;
  --yellow:#FFC23D; --blue:#3AA0FF; --blue-dk:#1A5AA8; --tangerine:#FF8A1E;

  /* Surfaces */
  --glass:rgba(255,255,255,0.78); --glass-strong:rgba(255,255,255,0.9); --white:#fff;
  --shadow-blue:rgba(34,90,160,0.16);

  /* Per-app, overridden inline on every .app from its own record. The default
     is the studio blue, so an app with no accent still renders sanely. */
  --accent:#1C6FC4; --accent-ink:#FFFFFF;

  --r-sm:12px; --r-md:20px; --r-lg:26px; --r-xl:36px; --r-pill:999px;

  /* Motion — docs/19. `linear` is never used for UI. --bounce overshoots to
     1.06 and settles; it is the studio's voice, and the same overshoot the
     game's departure gets for free from its kinematics. */
  --ease:cubic-bezier(.4,0,.2,1);
  --ease-in:cubic-bezier(0,0,.2,1);
  --ease-out:cubic-bezier(.4,0,1,1);
  --bounce:cubic-bezier(.34,1.56,.64,1);
  --t-micro:150ms; --t-std:280ms; --t-seq:560ms;

  --font-display:'Fredoka', ui-rounded, 'Segoe UI', system-ui, sans-serif;
  --font-body:'Nunito', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;

  --maxw:1180px; --nav-h:72px;
  --safe-t:env(safe-area-inset-top,0px); --safe-b:env(safe-area-inset-bottom,0px);
}

/* ── Reset ────────────────────────────────────────────────────────────────── */
*,*::before,*::after { box-sizing:border-box; margin:0; padding:0; }
html { scroll-behavior:smooth; -webkit-text-size-adjust:100%; }
body {
  font-family:var(--font-body); font-weight:600;
  color:var(--ink-800); background:var(--sky-2);
  overflow-x:hidden; -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility; min-height:100vh; line-height:1.6;
}
img { max-width:100%; display:block; }
/* Rule 7, restated after it bit again. `a:not(.btn)` has specificity (0,1,1),
   which out-ranks EVERY single-class component rule — .nav__link and .skip both
   silently lost their colour to it, and the nav measured 4.3:1 (C21 caught it).
   The bare element selector is (0,0,1), so every component class wins, and the
   buttons stay safe because each variant (.btn--primary / --ghost / --play)
   defines its own colour at (0,1,0). A build check asserts that: no <a
   class="btn"> ships without a variant. */
a { color:var(--ink-600); }
::selection { background:#FFD27A; color:var(--ink-900); }
:focus-visible { outline:3px solid var(--orange-dk); outline-offset:3px; border-radius:6px; }

/* Visible to a screen reader, to nothing else. Used for the "(opens in a new
   tab)" note on store buttons — sighted users get the same information from
   the tab appearing; a screen-reader user gets no warning at all without it. */
.sr-only {
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; border:0;
}

.skip {
  position:absolute; left:-9999px; top:0; z-index:100;
  background:#fff; color:var(--ink-900); padding:12px 20px;
  border-radius:0 0 var(--r-md) 0; font-family:var(--font-display); font-weight:600;
}
.skip:focus { left:0; }

/* ── Backdrop ─────────────────────────────────────────────────────────────── */
/* One fixed gradient. No canvas, no script, no compositing churn. */
.bg-fixed {
  position:fixed; inset:0; z-index:0; pointer-events:none;
  background:
    radial-gradient(120% 80% at 50% -10%, rgba(255,255,255,0.9) 0%, rgba(255,255,255,0) 42%),
    radial-gradient(80% 60% at 85% 8%, rgba(255,240,180,0.55) 0%, rgba(255,240,180,0) 55%),
    linear-gradient(180deg, var(--sky-0) 0%, var(--sky-1) 34%, var(--sky-3) 66%, var(--sky-4) 100%);
}
.page { position:relative; z-index:2; }
.wrap { max-width:var(--maxw); margin:0 auto; }

/* ── Navigation ───────────────────────────────────────────────────────────── */
.nav-wrap { position:fixed; top:0; left:0; right:0; z-index:30; }
.nav {
  display:flex; align-items:center; justify-content:space-between; gap:16px;
  padding:calc(13px + var(--safe-t)) clamp(16px,4vw,48px) 13px;
  background:rgba(255,255,255,0.72);
  backdrop-filter:blur(14px); -webkit-backdrop-filter:blur(14px);
  border-bottom:2px solid rgba(255,255,255,0.7);
  box-shadow:0 6px 22px rgba(34,90,160,0.12);
}
.nav__brand { display:flex; align-items:center; gap:11px; text-decoration:none; color:var(--ink-900); }
.nav__brand img { height:44px; width:auto; filter:drop-shadow(0 3px 5px rgba(20,58,94,0.25)); }
.nav__brand span {
  font-family:var(--font-display); font-weight:600; font-size:19px;
  color:var(--ink-900); line-height:1;
}
/* The CTA is a FLEX SIBLING of the links, never positioned over them.
   Golden rule 8 — a positioned CTA once covered "Support" and ate its taps. */
.nav__links { display:flex; align-items:center; gap:clamp(12px,2.2vw,28px); }
.nav__link {
  font-family:var(--font-display); font-weight:600; font-size:16px;
  color:var(--ink-900); text-decoration:none; transition:color .18s; white-space:nowrap;
}
.nav__link:hover { color:var(--orange-ink); }
/* WCAG 2.2 SC 2.5.8 wants 24x24. These measured 43x22 and 21x21 on a phone —
   close enough to pass on the spacing exception, and not close enough to ship a
   game on. The page is about to be an installed app on somebody's home screen. */
.nav__link { display:inline-flex; align-items:center; min-height:24px; }

/* The UA stylesheet gives b/strong `font-weight: bolder`, which is RELATIVE:
   inherited 700 computes to 900, and Nunito ships as a 400-700 variable range.
   With no 900 to reach for, the browser fakes one by smearing the 700 — which
   is what "the font is not right" looks like on a paragraph. An absolute weight
   instead. */
b, strong { font-weight:700; }

/* ── Buttons ──────────────────────────────────────────────────────────────── */
/* The DEFAULT size lives on .btn itself, and the modifiers below override it by
   coming later at the same specificity. It used to live on
   `.btn:not(.btn--lg):not(.btn--md):not(.btn--sm)` — specificity (0,4,0), which
   out-ranks any two-class component rule. `.app-cta .btn` is (0,2,0), so the
   font-size and padding it sets for the card's CTA row were silently dead and
   the row was running on defaults that happened to fit. Golden rule 7, in the
   one place it had not been cleaned up. */
.btn {
  display:inline-flex; align-items:center; justify-content:center; gap:10px;
  font-family:var(--font-display); font-weight:600; text-decoration:none; cursor:pointer;
  border:none; border-radius:var(--r-pill); text-align:center;
  font-size:16px; padding:13px 24px;
  transition:transform var(--t-micro) var(--bounce), filter .18s, box-shadow var(--t-micro) var(--ease);
  user-select:none; -webkit-tap-highlight-color:transparent;
}
.btn--lg { font-size:19px; padding:17px 32px; }
.btn--md { font-size:16px; padding:12px 22px; }
.btn--sm { font-size:14px; padding:9px 16px; }

.btn--primary {
  color:var(--on-orange); background:linear-gradient(180deg,var(--orange-lt),var(--orange));
  box-shadow:0 8px 0 var(--orange-dk), 0 14px 26px rgba(201,94,0,0.4);
}
.btn--primary.btn--md { box-shadow:0 5px 0 var(--orange-dk), 0 9px 16px rgba(201,94,0,0.34); }
.btn--primary:hover { filter:brightness(1.05); }
.btn--primary:hover { filter:brightness(1.05); transform:translateY(-2px); }
.btn--primary:active { transform:translateY(6px) scale(.96,1.04); box-shadow:0 2px 0 var(--orange-dk), 0 6px 12px rgba(201,94,0,0.4); }

.btn--ghost {
  color:var(--ink-700); background:#fff;
  box-shadow:0 8px 0 #B9D6EE, 0 14px 26px rgba(34,90,160,0.2);
}
.btn--ghost.btn--md { box-shadow:0 5px 0 #B9D6EE, 0 9px 16px rgba(34,90,160,0.2); }
.btn--ghost:hover { filter:brightness(1.02); }
.btn--ghost:hover { filter:brightness(1.02); transform:translateY(-2px); }
.btn--ghost:active { transform:translateY(6px) scale(.96,1.04); box-shadow:0 2px 0 #B9D6EE, 0 6px 12px rgba(34,90,160,0.2); }

/* The Play button. The triangle is DRAWN, not Google's badge image — rule 3
   says no third-party request, and re-hosting their artwork would also mean
   shipping their trademark from our origin. */
.btn--play {
  color:#fff; background:linear-gradient(180deg,#3E4046,#26282D); font-size:16px; padding:13px 24px;
  box-shadow:0 5px 0 #17181C, 0 10px 20px rgba(20,30,50,0.28);
}
.btn--play:hover { transform:translateY(-2px); filter:brightness(1.12); }
.btn--play:active { transform:translateY(4px) scale(.96,1.04); box-shadow:0 1px 0 #17181C; }
.gplay-tri {
  width:0; height:0; flex:0 0 auto;
  border-left:12px solid currentColor;
  border-top:7px solid transparent; border-bottom:7px solid transparent;
}
.btn--ghost .gplay-tri { border-left-color:var(--blue); }

.btn-row { display:flex; flex-wrap:wrap; gap:14px; align-items:center; }

/* ── Type ─────────────────────────────────────────────────────────────────── */
.h1, h1 {
  font-family:var(--font-display); font-weight:600;
  font-size:clamp(34px,6vw,64px); line-height:1.04; color:var(--ink-900);
  text-shadow:0 2px 0 rgba(255,255,255,0.7); margin-bottom:.5em;
}
.h2, h2 {
  font-family:var(--font-display); font-weight:600;
  font-size:clamp(28px,4.4vw,46px); line-height:1.08; color:var(--ink-900);
  margin-bottom:.5em;
}
.h3, h3 {
  font-family:var(--font-display); font-weight:600; font-size:22px;
  color:var(--ink-900); line-height:1.25; margin-bottom:.4em;
}
h4 { font-family:var(--font-display); font-weight:600; font-size:17px; color:var(--ink-900); margin-bottom:.4em; }
.lede { font-size:clamp(16px,2.05vw,20px); color:var(--ink-800); line-height:1.55; }
.muted { color:var(--ink-400); }
.accent { color:var(--orange-ink); }

.eyebrow {
  display:inline-flex; align-items:center; gap:8px;
  font-family:var(--font-display); font-weight:600; font-size:14px; color:var(--ink-700);
  background:rgba(255,255,255,0.82); border:2px solid #fff; border-radius:var(--r-pill);
  padding:7px 16px; box-shadow:0 6px 16px rgba(34,90,160,0.16);
  letter-spacing:.4px; text-transform:uppercase; margin-bottom:14px;
}
.eyebrow .dot {
  width:9px; height:9px; border-radius:50%; background:var(--green);
  box-shadow:0 0 0 4px rgba(54,201,141,0.25);
}

/* ── Layout ───────────────────────────────────────────────────────────────── */
main > .wrap { padding:0 clamp(16px,4vw,48px); }
.section { padding:20px 0 76px; }
.section-head { text-align:center; max-width:44rem; margin:0 auto 42px; }
.glass-panel {
  background:var(--glass);
  backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
  border:2px solid #fff; border-radius:var(--r-xl);
  padding:clamp(28px,5vw,58px) clamp(20px,4vw,52px);
  box-shadow:0 24px 60px rgba(34,90,160,0.22);
}

/* ── S1 · Hero ────────────────────────────────────────────────────────────── */
.hero {
  min-height:100vh; display:flex; flex-direction:column;
  align-items:center; justify-content:center; text-align:center;
  padding:calc(120px + var(--safe-t)) 8px 64px;
}
/* The floating logo. This is the animation the original site had, restored
   verbatim — it is NOT the bouncing-blocks canvas, which is gone. */
/* 256, not the original's 300. The logo master is 512 px, so 256 CSS px is
   exactly what a DPR-2 screen can draw sharply — at 300 the browser was
   stretching the 2x candidate by 17%, which C33 now fails the run for. A
   crisp 256 beats a soft 300. A larger master would buy the size back. */
.hero__logo { width:min(256px,62vw); height:auto; margin:20px auto 4px; }
.hero__logo img {
  width:100%; height:auto;
  filter:drop-shadow(0 16px 26px rgba(20,58,94,0.35));
  transform-origin:50% 60%;
}
.hero .lede { max-width:54ch; margin:18px auto 0; }
.hero__cta { display:flex; flex-wrap:wrap; gap:16px; justify-content:center; margin-top:34px; }
.stats { display:flex; flex-wrap:wrap; gap:14px; justify-content:center; margin-top:50px; }
.stat {
  background:var(--glass-strong); border:2px solid #fff; border-radius:var(--r-md);
  padding:15px 26px; box-shadow:0 8px 20px rgba(34,90,160,0.14);
  animation:pf-bobslow 6s ease-in-out infinite;
}
.stat:nth-child(2) { animation-delay:-1.5s; }
.stat:nth-child(3) { animation-delay:-3s; }
.stat:nth-child(4) { animation-delay:-4.5s; }
.stat__num { font-family:var(--font-display); font-weight:600; font-size:30px; line-height:1; color:var(--ink-900); }
.stat__label {
  font-weight:700; font-size:13px; color:var(--ink-700); margin-top:4px;
  text-transform:uppercase; letter-spacing:.6px;
}
.scroll-cue {
  margin-top:42px; font-size:30px; color:var(--ink-700);
  animation:pf-arrow 1.6s ease-in-out infinite;
  filter:drop-shadow(0 2px 3px rgba(255,255,255,.6));
}

/* ── S2 · How we work ─────────────────────────────────────────────────────── */
.principle-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(250px,1fr)); gap:26px; }
.principle, .promises li, .feature, .panel {
  position:relative; background:#fff; border-radius:var(--r-lg); padding:26px 24px;
  box-shadow:0 12px 30px rgba(34,90,160,0.14); border:2px solid #EEF5FB;
  transition:transform var(--t-std) var(--bounce);
}
/* The shadow is the SECONDARY action: it answers the lift instead of being
   part of it. Animating the real box-shadow would repaint a blur every frame. */
.principle::after, .promises li::after, .feature::after, .panel::after {
  content:""; position:absolute; inset:0; border-radius:inherit; z-index:-1;
  box-shadow:0 26px 46px rgba(34,90,160,0.28);
  opacity:0; transition:opacity var(--t-std) var(--ease);
}
.principle:hover, .promises li:hover, .feature:hover, .panel:hover { transform:translateY(-6px); }
.principle:hover::after, .promises li:hover::after,
.feature:hover::after, .panel:hover::after { opacity:1; }
.principle p { color:var(--ink-500); font-size:16px; }

/* ── S3 · The app section ─────────────────────────────────────────────────── */
/* One per App record. `--accent` comes off the record inline, which is what
   makes two apps look like two products with no second selector. The stage
   alternates sides on `data-side`, so a stack of them reads as a rhythm. */
.app {
  display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  align-items:stretch; margin:0 0 40px;
  border-radius:var(--r-xl); overflow:hidden;
  box-shadow:0 24px 60px rgba(34,90,160,0.24); border:2px solid #fff;
  background:#fff;
}
.app[data-side="right"] .app-stage { order:2; }

.app-stage {
  position:relative; display:flex; flex-direction:column;
  align-items:center; justify-content:center; gap:18px;
  padding:clamp(28px,4vw,48px) clamp(18px,3vw,36px);
  background:
    radial-gradient(90% 70% at 50% 0%, rgba(255,255,255,.22) 0%, rgba(255,255,255,0) 60%),
    linear-gradient(160deg, var(--accent) 0%, #101827 130%);
}

/* The rig holds the phone and its two arrows. The arrows sit OUTSIDE the
   phone body so they never cover the screenshot, and they are vertically
   centred on it rather than on the rig, which matters once the dots are in
   play. 44x44 minimum, per WCAG 2.2 target size. */
/* THREE COLUMNS: arrow, phone, arrow. They were absolutely positioned at
   `50% - phone/2 - 26px`, which put each disc half over the screenshot — and
   at 390px there was not 52px of clear stage beside the phone to put them in
   anyway. A grid cannot overlap: the phone takes whatever is left after both
   arrows and the gaps, at every width, with no media query to get wrong. */
.phone-rig {
  position:relative; width:100%;
  display:grid; align-items:center; justify-content:center;
  grid-template-columns:auto minmax(0,var(--phone-w)) auto;
  gap:clamp(8px,2.2vw,20px);
}
.shot-nav {
  z-index:5; width:var(--nav-d); height:var(--nav-d);
  border:none; border-radius:50%; cursor:pointer;
  display:grid; place-items:center; padding:0;
  color:var(--ink-900); background:#fff;
  box-shadow:0 8px 22px rgba(8,20,40,.45), 0 0 0 2px rgba(255,255,255,.5);
  transition:transform var(--t-std) var(--bounce), box-shadow var(--t-micro) var(--ease);
  -webkit-tap-highlight-color:transparent;
}
/* All three on ROW 1 explicitly. Auto-placement is sparse: after the two
   buttons claim columns 1 and 3 the cursor sits past column 3, so a phone
   asking for column 2 gets pushed to a second row — which is exactly what
   happened, and the arrows sat above the screenshot instead of beside it. */
.shot-prev { grid-column:1; grid-row:1; }
.shot-next { grid-column:3; grid-row:1; }
/* ONE chevron, drawn from two borders. The button used to contain &lsaquo; as
   well, so a glyph and a drawn chevron rendered on top of each other. */
.shot-nav .chev {
  display:block; width:12px; height:12px;
  border-top:3px solid currentColor; border-right:3px solid currentColor;
}
.shot-prev .chev { transform:rotate(-135deg) translate(-2px,-2px); }
.shot-next .chev { transform:rotate(45deg) translate(-2px,2px); }
.shot-nav:hover { transform:scale(1.14); box-shadow:0 12px 28px rgba(8,20,40,.5), 0 0 0 2px #fff; }
.shot-nav:active { transform:scale(.9); }
/* The strip WRAPS, so neither arrow is ever disabled and neither is ever
   dimmed. A 30%-opacity white disc on this gradient is invisible, which is why
   the previous arrow read as missing; and with the dots underneath carrying
   the position, a wrap cannot be mistaken for a broken control. */
.phone-rig:hover .shot-nav { transform:translateY(-4px); }
.phone-rig:hover .shot-nav:hover { transform:translateY(-4px) scale(1.14); }

/* The phone. Drawn in CSS: no device-frame image, so no extra request and no
   licence question about somebody's product photograph. */
.app-stage { --phone-w:272px; --nav-d:52px; }
/* Narrow: the arrows are now real columns, so every pixel of side padding is a
   pixel off the phone. 46px is still over WCAG 2.2's 44px target minimum. */
@media (max-width:30rem) {
  .app-stage { --nav-d:46px; padding-inline:10px; }
}
.phone {
  position:relative; grid-column:2; grid-row:1; width:100%; aspect-ratio:9/16;
  background:#0C1220; border-radius:36px; padding:9px;
  box-shadow:0 0 0 2px rgba(255,255,255,.18), 0 26px 50px rgba(8,20,40,.5);
  transition:transform var(--t-std) var(--bounce), box-shadow var(--t-std) var(--ease);
  will-change:transform;
}
.phone-rig:hover .phone {
  transform:translateY(-10px) rotate(-1.2deg) scale(1.025);
  box-shadow:0 0 0 2px rgba(255,255,255,.28), 0 40px 70px rgba(8,20,40,.62);
}
.phone-notch {
  position:absolute; top:13px; left:50%; transform:translateX(-50%);
  width:34%; height:16px; background:#0C1220; border-radius:var(--r-pill); z-index:3;
}
/* A scroll-snap strip. Every screenshot is in the DOM and reachable by
   scrolling, so this works with scripting off (docs/17 A8). */
.shots {
  list-style:none; display:flex; height:100%; overflow-x:auto; overflow-y:hidden;
  scroll-snap-type:x mandatory; border-radius:28px; background:#000;
  scrollbar-width:none;
}
.shots::-webkit-scrollbar { display:none; }
.shots > li {
  position:relative; flex:0 0 100%; scroll-snap-align:center; height:100%;
}
.shots > li picture, .shots > li img { width:100%; height:100%; object-fit:cover; }
.shot-dots { list-style:none; display:flex; flex-wrap:wrap; gap:2px; justify-content:center; }
.shot-dots a {
  display:grid; place-items:center; width:26px; height:26px; border-radius:50%;
  -webkit-tap-highlight-color:transparent;
}
.shot-dots span {
  display:block; width:9px; height:9px; border-radius:50%;
  background:rgba(255,255,255,.45); transition:background .18s, transform .18s;
}
.shot-dots a:hover span, .shot-dots a:focus-visible span { background:#fff; transform:scale(1.25); }
.shot-dots a[aria-current="true"] span { background:#fff; transform:scale(1.3); }

.app-detail {
  display:flex; flex-direction:column; align-items:flex-start; justify-content:center; gap:16px;
  padding:clamp(28px,4vw,52px) clamp(22px,3.4vw,46px); background:#fff;
}
.app-id { display:flex; align-items:center; gap:16px; }
.app-icon { width:72px; height:72px; border-radius:18px; box-shadow:0 10px 22px rgba(34,90,160,.26); }
.app-id h3 { margin:0; font-size:clamp(26px,3vw,34px); }
.app-cat {
  margin:2px 0 0; font-size:13px; font-weight:700; letter-spacing:.8px;
  text-transform:uppercase; color:var(--ink-400);
}
.app-tagline { font-family:var(--font-display); font-weight:500; font-size:20px; color:var(--ink-700); margin:0; }
.app-summary { font-size:16px; color:var(--ink-500); margin:0; }

.tags { list-style:none; display:flex; flex-wrap:wrap; gap:8px; }
.tags li {
  font-family:var(--font-display); font-weight:500; font-size:13.5px;
  color:var(--accent); background:color-mix(in srgb, var(--accent) 9%, #fff);
  border:1.5px solid color-mix(in srgb, var(--accent) 26%, #fff);
  border-radius:var(--r-pill); padding:6px 14px;
}
.app-facts { list-style:none; display:flex; flex-wrap:wrap; gap:12px 26px; margin:2px 0; }
.app-facts b {
  display:block; font-family:var(--font-display); font-weight:600;
  font-size:22px; color:var(--ink-900); line-height:1.1;
}
.app-facts span { font-size:12px; font-weight:700; text-transform:uppercase; letter-spacing:.6px; color:var(--ink-400); }
/* Two buttons, one row, equal share. They were stacking because each was
   sized to its own label; `1 1 0` makes them split the space instead, and
   min-width:0 stops the longer label from forcing a wrap. */
/* ONE ROW, always. `@media (max-width:26rem)` used to send each button to
   `flex:1 1 100%`, and 26rem is 416px — so every phone got the stacked pair
   the user reported. The row survives 320px instead by letting the labels wrap
   and shrinking the type with the container, not by giving up the row. */
.app-cta { display:flex; gap:12px; margin-top:4px; width:100%; align-items:stretch; }
.app-cta .btn {
  flex:1 1 0; min-width:0; padding:11px clamp(8px,1.4vw,16px);
  font-size:clamp(13px,2.1vw,16px); line-height:1.2;
  display:flex; align-items:center; justify-content:center; gap:clamp(5px,1.2vw,9px);
}
/* The store's own lockup: a small "GET IT ON" over "Google Play". Two
   deliberate lines instead of three accidental ones — and it is the shape
   people already recognise.
   
   The badge's proportion is the point: on the real thing the ICON IS AS TALL AS
   THE TEXT BLOCK. Ours was 12px against 29 — 41% — with a 9px gap, which reads
   as a stray arrow parked next to some words rather than as one mark. Every
   dimension below is in `em`, so the relationship survives the type shrinking
   with the container down to 320px. */
.gplay-txt { display:flex; flex-direction:column; align-items:flex-start; line-height:1; }
.gplay-txt small { font-size:.6em; letter-spacing:.1em; opacity:.85; font-weight:600; white-space:nowrap;
                   line-height:1.25; }
.gplay-txt b { font-size:1.14em; font-weight:600; letter-spacing:-.015em; white-space:nowrap;
               line-height:1.15; }
/* Both buttons carry the SAME 3-D lip here. The orange one's default lip is
   8px and the store button's is 5px, which made an equal pair look unequal —
   the shadow is part of the object's size even though it is not part of its
   box. */
.app-cta .btn--primary { box-shadow:0 5px 0 var(--orange-dk), 0 9px 16px rgba(201,94,0,0.34); }
.app-cta .btn--primary:active { box-shadow:0 2px 0 var(--orange-dk), 0 6px 12px rgba(201,94,0,0.4); }
/* Sized off the button's own font, to the badge's ratio: the triangle stands
   as tall as GET IT ON + Google Play together. `gap` is the badge's ~0.3 x the
   icon's width. */
.app-cta .gplay-tri {
  border-left-width:1.4em; border-top-width:.88em; border-bottom-width:.88em;
}
.app-cta .btn--play { gap:.32em; }

.apps-all { text-align:center; margin-top:8px; }
.apps-all a {
  font-family:var(--font-display); font-weight:600; font-size:17px;
  color:var(--ink-900); text-decoration:none;
  border-bottom:2px solid rgba(28,86,137,.3); padding-bottom:2px;
}
.apps-all a:hover { color:var(--orange-ink); border-bottom-color:var(--orange); }

.pill {
  display:inline-block; font-family:var(--font-display); font-weight:500; font-size:13px;
  border-radius:var(--r-pill); padding:5px 13px; vertical-align:middle;
}
.pill-soon { background:var(--yellow); color:#5A3D00; margin-left:10px; }

/* ── S4 · The studio ──────────────────────────────────────────────────────── */
.studio-panel { display:grid; grid-template-columns:minmax(0,.85fr) minmax(0,1.15fr); gap:clamp(28px,4vw,52px); align-items:center; }
.studio-art img {
  width:100%; height:auto; border-radius:28px; border:4px solid #fff;
  box-shadow:0 22px 50px rgba(34,90,160,0.3);
  animation:pf-floaty 7s ease-in-out infinite;
}
.studio-copy .lede { margin-bottom:26px; }
.about-grid { display:grid; gap:22px; }
.about-grid p { color:var(--ink-500); font-size:16px; }

/* ── S5 · Contact ─────────────────────────────────────────────────────────── */
.contact-panel { text-align:center; }
.contact-panel .lede { max-width:52ch; margin:0 auto; }
.promises {
  list-style:none; display:grid; grid-template-columns:repeat(auto-fit,minmax(220px,1fr));
  gap:20px; margin:34px 0 30px; text-align:left;
}
.promises li { background:#fff; border-radius:var(--r-lg); padding:20px 22px; box-shadow:0 10px 26px rgba(34,90,160,.14); }
.promises b { display:block; font-family:var(--font-display); font-weight:600; font-size:18px; color:var(--ink-900); margin-bottom:5px; }
.promises span { font-size:15px; color:var(--ink-500); }
/* The analytics off switch on /privacy/. Rendered only when there is
   something to switch off. */
.optout { margin:22px 0 8px; }
/* Inline links in long-form prose are exempt from SC 2.5.8 (they are "in a
   sentence"), but the standalone link ROWS at the foot of each legal page are
   controls, not prose. */
.prose main > .wrap > p:last-child a { display:inline-flex; align-items:center; min-height:26px; }
.contact-cta { margin-bottom:16px; }
/* The label is a whole email address — one unbreakable token 31 characters
   long. At 320px it was 354px wide inside a 288px panel and pushed the whole
   document sideways. It wraps at the punctuation now, and the type gives way
   before the layout does. */
.contact-cta .btn {
  max-width:100%; overflow-wrap:anywhere;
  font-size:clamp(15px,4.4vw,19px); padding-inline:clamp(16px,5vw,32px);
}

/* ── S6 · Footer ──────────────────────────────────────────────────────────── */
.footer {
  background:linear-gradient(180deg,var(--ink-900),#0A2C4F);
  padding:54px clamp(18px,4vw,48px) calc(34px + var(--safe-b));
  color:#CFE2F4; position:relative; z-index:2; margin-top:40px;
}
.footer__inner { max-width:var(--maxw); margin:0 auto; }
.footer__inner { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,2fr); gap:36px; }
.footer__brand { max-width:340px; }
.footer__brand .nav__brand, .footer__brand .nav__brand span { color:#fff; }
.footer__brand p { font-size:15px; line-height:1.55; color:#9FC0DE; margin-top:12px; }
.footer__cols { display:grid; grid-template-columns:repeat(auto-fit,minmax(140px,1fr)); gap:26px; }
.footer__cols h4 { color:#fff; font-size:15px; }
.footer__cols ul { list-style:none; }
.footer__cols li { margin-bottom:5px; }
.footer__cols a { display:flex; align-items:center; min-height:26px; font-weight:700; font-size:15px; color:#9FC0DE; text-decoration:none; transition:color .18s; overflow-wrap:anywhere; }
.footer__cols a:hover { color:var(--orange-lt); }
/* The press kit's table is a list of things to copy and open — every cell is a
   control, not prose. */
table.stats td a, .dl a { display:inline-flex; align-items:center; min-height:26px; }
.footer__note {
  grid-column:1 / -1; margin-top:14px; padding-top:22px;
  border-top:1px solid rgba(255,255,255,.14);
  font-weight:700; font-size:13px; color:#9FC0DE;
}
/* The privacy note carries a link once analytics is on, and the generic `a`
   colour is a dark blue meant for a white page — 2.27:1 on this background.
   Same class of bug as the footer brand link (sprint 10), same fix: a footer
   link takes the footer's own colour. */
.footer__note a { color:#CFE2F4; text-underline-offset:3px; }
.footer__note a:hover { color:var(--orange-lt); }

/* The press page's specification table. `.stats` is also the hero's fact-tile
   row, so every rule for it here is tag-scoped — an unscoped `.stats` selector
   would restyle the hero. */
table.stats { width:100%; border-collapse:collapse; background:#fff; border-radius:var(--r-lg); overflow:hidden; box-shadow:0 12px 30px rgba(34,90,160,.14); margin-bottom:32px; display:table; }
table.stats th, table.stats td { text-align:left; padding:13px 20px; border-bottom:1px solid #E6EFF7; font-size:15.5px; }
table.stats th { width:34%; color:var(--ink-700); font-weight:700; }
table.stats td { color:var(--ink-500); }

/* ── Inner pages ──────────────────────────────────────────────────────────── */
.crumbs { padding:calc(var(--nav-h) + 28px) 0 18px; font-weight:700; font-size:14px; color:var(--ink-900); }
/* The breadcrumb sits on the sky, not on a panel, so it cannot use the
   generic link ink — that measured 2.72:1 there. */
/* A breadcrumb is a navigation ROW, not a link inside a sentence, so SC 2.5.8's
   "in a block of text" exception does not cover it. */
.crumbs a { text-decoration:underline; color:var(--ink-900);
  display:inline-flex; align-items:center; min-height:26px; }
.crumbs a:hover { color:var(--orange-ink); }
main > .wrap > h1:first-child { padding-top:calc(var(--nav-h) + 34px); }

/* `prose` is set on <body>, so every one of these MUST be scoped to `main` —
   an unscoped `.prose li` matched the footer's link lists and repainted them
   ink-on-ink at 1.01:1. Inheritance always loses to a matching rule, so the
   footer's own `color` never stood a chance. C21 caught it; the lesson is that
   a body-level class is a document-wide selector. */
/* Long-form pages get a paper card. Not decoration: legal prose was sitting
   directly on the sky gradient, where the link ink measured 2.72:1 and .muted
   2.96:1. On paper the same tokens are 5.3 and 6.4. It also reads better —
   nobody wants a terms-of-use page over a photographic sky. */
.prose main > .wrap {
  max-width:min(74ch, var(--maxw)); background:#fff; border-radius:var(--r-xl);
  padding:clamp(26px,4vw,54px) clamp(20px,4vw,52px) clamp(34px,5vw,60px);
  margin-top:calc(var(--nav-h) + 26px); margin-bottom:40px;
  box-shadow:0 24px 60px rgba(34,90,160,0.22); border:2px solid #fff;
}
.prose main > .wrap > h1:first-child, .prose main .crumbs { padding-top:0; }
.prose main { max-width:none; }
.prose main p, .prose main li { color:var(--ink-800); font-size:16.5px; margin-bottom:1em; }
.prose main h2 { margin-top:1.4em; }
.prose main h3 { margin-top:1.2em; }
.prose main ul, .prose main ol { padding-left:1.3em; margin-bottom:1em; }

.page-head { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,.8fr); gap:36px; align-items:center; margin-bottom:40px; }
.hero-shot img { border-radius:22px; border:4px solid #fff; box-shadow:0 18px 40px rgba(34,90,160,.28); }
.rule-card { background:#fff; border-radius:var(--r-lg); padding:24px 26px; box-shadow:0 12px 30px rgba(34,90,160,.16); margin-bottom:26px; }
.panel-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(250px,1fr)); gap:22px; margin-bottom:32px; }
.panel { background:#fff; border-radius:var(--r-lg); padding:24px; box-shadow:0 12px 30px rgba(34,90,160,.14); }
.feature-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(250px,1fr)); gap:22px; margin-bottom:32px; }
.feature { background:#fff; border-radius:var(--r-lg); padding:22px 24px; box-shadow:0 12px 30px rgba(34,90,160,.14); }
.gallery { display:grid; grid-template-columns:repeat(auto-fit,minmax(190px,1fr)); gap:20px; margin-bottom:32px; list-style:none; }
.gallery img { border-radius:18px; border:3px solid #fff; box-shadow:0 12px 28px rgba(34,90,160,.22); }
.gallery p { font-size:14px; color:var(--ink-500); margin-top:8px; }
.spec { width:100%; border-collapse:collapse; background:#fff; border-radius:var(--r-lg); overflow:hidden; box-shadow:0 12px 30px rgba(34,90,160,.14); margin-bottom:32px; }
.spec th, .spec td { text-align:left; padding:13px 20px; border-bottom:1px solid #E6EFF7; font-size:15.5px; }
.spec th { width:34%; color:var(--ink-700); font-weight:700; }
.spec td { color:var(--ink-500); }
.spec tr:last-child th, .spec tr:last-child td { border-bottom:none; }
.faq { background:#fff; border-radius:var(--r-lg); padding:4px 24px; box-shadow:0 12px 30px rgba(34,90,160,.14); margin-bottom:32px; }
.faq details + details { border-top:1px solid #E6EFF7; }
.faq summary {
  display:flex; align-items:center; justify-content:space-between; gap:16px;
  font-family:var(--font-display); font-weight:600; font-size:17px;
  color:var(--ink-900); padding:18px 0; cursor:pointer; list-style:none;
}
/* The UA marker is replaced, not hidden-and-forgotten: .chev is the control's
   only visible state, so it has to be drawn and it has to move. */
.faq summary::-webkit-details-marker { display:none; }
.faq summary::marker { content:""; }
.faq summary:hover .q { color:var(--orange-ink); }
.faq .chev {
  flex:0 0 auto; width:11px; height:11px; border-right:2.5px solid var(--ink-400);
  border-bottom:2.5px solid var(--ink-400); transform:rotate(45deg) translateY(-2px);
  transition:transform var(--t-std) var(--bounce), border-color var(--t-std) var(--ease);
}
.faq details[open] .chev { transform:rotate(-135deg) translateY(-2px); border-color:var(--orange); }
.faq .a { overflow:hidden; }
.faq details[open] .a { animation:pf-open var(--t-std) var(--ease-in) both; }
@keyframes pf-open { from { opacity:0; transform:translateY(-6px); } to { opacity:1; transform:none; } }
.faq p { color:var(--ink-500); padding-bottom:18px; font-size:16px; margin:0; }

.game-card {
  display:grid; grid-template-columns:auto minmax(0,1fr); gap:22px; align-items:start;
  background:#fff; border-radius:var(--r-lg); padding:26px;
  box-shadow:0 12px 30px rgba(34,90,160,.16); border:2px solid #EEF5FB; margin-bottom:24px;
}
.game-card > img { width:72px; height:72px; border-radius:18px; }
.game-card p { color:var(--ink-500); margin:8px 0 14px; }

.credit { font-size:13px; color:var(--ink-400); }

/* ── Responsive ───────────────────────────────────────────────────────────── */
@media (max-width:60rem) {
  .app { grid-template-columns:1fr; }
  /* The stage is always FIRST on a phone. `order:2` on a one-column grid would
     put the screenshots below the buttons, which reads as an afterthought. */
  .app[data-side="right"] .app-stage { order:0; }
  .studio-panel { grid-template-columns:1fr; }
  .page-head { grid-template-columns:1fr; }
  .footer__inner { grid-template-columns:1fr; }
}
@media (max-width:48rem) {
  .nav__links { gap:12px; }
  .nav__link { font-size:15px; }
  .hero { min-height:auto; padding-top:calc(104px + var(--safe-t)); }
  /* 3 + 1 read as a mistake. Two rows of two is a shape. */
  .app-facts { display:grid; grid-template-columns:1fr 1fr; gap:10px 16px; }
  .app-facts b { font-size:19px; }
}
@media (max-width:34rem) {
  .nav__brand span { display:none; }
  table.stats th, table.stats td { display:block; width:auto; padding:8px 18px; }
  table.stats th { padding-bottom:0; border-bottom:none; }
  .nav__link { font-size:14px; }
  .btn--md { padding:10px 15px; font-size:15px; }
  .hero__cta .btn { width:100%; }
  .spec th, .spec td { display:block; width:auto; padding:8px 18px; }
  .spec th { padding-bottom:0; border-bottom:none; }
}

/* ── The hero entrance (docs/19) ──────────────────────────────────────────
   One orchestrated moment, 70 ms apart, rather than six scattered effects.
   The logo lands on the bounce curve and hands straight off into its permanent
   float — `pf-bob` starts after `pf-land` finishes, so there is no seam and no
   second keyframe fighting the first for the same transform.

   These run from CSS on load rather than from JavaScript: the hero is the
   first paint, and waiting for a script to reveal it is how a fast connection
   ends up looking slower than a slow one. */
@keyframes pf-rise { from { opacity:0; transform:translateY(16px); } to { opacity:1; transform:none; } }
@keyframes pf-land { 0% { opacity:0; transform:translateY(-28px) scale(.86); }
                     100% { opacity:1; transform:none; } }

.hero > * { animation:pf-rise var(--t-seq) var(--ease-in) both; }
.hero .eyebrow   { animation-delay:0ms; }
.hero__logo      { animation:pf-land 620ms var(--bounce) 70ms both; }
.hero .h1        { animation-delay:140ms; }
.hero .lede      { animation-delay:210ms; }
.hero__cta       { animation-delay:280ms; }
.hero .stats     { animation-delay:350ms; }
.hero .scroll-cue{ animation-delay:420ms; }
/* The float waits for the landing to finish (70 + 620 ms) and then runs
   forever. Two animations on one element would otherwise both write
   `transform` and the last one declared would simply win. */
.hero__logo img { animation:pf-bob 5s ease-in-out 700ms infinite; }

/* ── Scroll reveal (docs/19) ──────────────────────────────────────────────
   site.js adds `js-reveal` to <html> and `reveal` to the targets. Without
   scripting neither class exists, so nothing is ever left hidden — the failure
   mode of every reveal implementation that hides in CSS and shows in JS. */
.js-reveal .reveal { opacity:0; transform:translateY(18px); }
.js-reveal .reveal.is-in {
  opacity:1; transform:none;
  transition:opacity var(--t-seq) var(--ease-in),
             transform var(--t-seq) var(--bounce);
  transition-delay:var(--reveal-delay, 0ms);
}

/* ── Keyframes ────────────────────────────────────────────────────────────── */
/* Restored verbatim from the original site. The logo float is the animation
   the user asked to keep; the physics canvas is the one that is gone. */
@keyframes pf-bob { 0%,100%{transform:translateY(0) rotate(-1.5deg);} 50%{transform:translateY(-16px) rotate(1.5deg);} }
@keyframes pf-floaty { 0%,100%{transform:translateY(0) rotate(-1deg);} 50%{transform:translateY(-12px) rotate(1deg);} }
@keyframes pf-bobslow { 0%,100%{transform:translateY(0);} 50%{transform:translateY(-9px);} }
@keyframes pf-arrow { 0%,100%{transform:translateY(0);} 50%{transform:translateY(7px);} }

@media (prefers-reduced-motion:reduce) {
  *, *::before, *::after {
    animation:none !important; scroll-behavior:auto !important; transition:none !important;
  }
  .shots { scroll-snap-type:none; }
}
