/* ==========================================================================
   Fantasy Hall — landing styles
   Dark premium · Sora throughout (the product's single UI face) · orange as accent
   Sections: tokens · base · layout · nav · hero · compat · problem · how
             product · features · trust · faq · final · footer
             · mobile CTA · motion · responsive
   ========================================================================== */

/* ---------------------------------------------------------------- tokens -- */
:root {
  --bg:            #07070a;
  --bg-2:          #0b0b10;
  --surface:       #101016;
  --surface-2:     #15151c;
  --line:          rgba(255, 255, 255, 0.07);
  --line-strong:   rgba(255, 255, 255, 0.12);

  --text:          #f3f3f6;
  --muted:         #9a9aa6;   /* 7.2:1 on --bg  */
  --faint:         #82828e;   /* 5.3:1 on --bg  */

  /* Channels, so every tint, line and glow derives from one definition
     instead of being retyped. 47 sites used to hard-code this triplet. */
  --accent-rgb:    255 122 26;
  --surface-rgb:   230 234 240;

  --accent:        rgb(var(--accent-rgb));
  --accent-soft:   #ff9a4d;
  --accent-deep:   #e8650a;
  --accent-tint:   rgb(var(--accent-rgb) / 0.10);
  --accent-line:   rgb(var(--accent-rgb) / 0.28);

  --green:         #4ade80;
  --green-tint:    rgba(74, 222, 128, 0.12);
  --red:           #f87171;
  --red-tint:      rgba(248, 113, 113, 0.12);
  --amber:         #F5A524;   /* the app's pending/warning token */
  --amber-tint:    rgba(245, 165, 36, 0.12);
  --blue:          #60a5fa;

  /* Product-surface tokens, copied verbatim from the Fantasy Hall app
     (tailwind.config.js + index.css) so the phone mockups are the real UI. */
  --app-bg:            #0E1117;
  --app-surface:       #161B22;
  --app-surface-hi:    #1C2128;
  --app-border:        rgb(var(--surface-rgb) / 0.08);
  --app-border-strong: rgb(var(--surface-rgb) / 0.14);
  --app-text:          #E6EAF0;
  --app-text-2:        #9AA4B2;
  --app-text-3:        #8994a6;
  --app-primary:       #FF7A1A;
  --app-emerald:       #2DBE84;
  --app-danger:        #F2545B;
  --app-warning:       #F5A524;
  --app-warning-tint:  rgba(245, 165, 36, 0.12);

  --display: 'Sora', system-ui, -apple-system, sans-serif;
  /* One family, like the product: DESIGN.md sets Sora as the single UI face. */
  --body: 'Sora', system-ui, -apple-system, sans-serif;

  --maxw: 1200px;
  --gutter: clamp(20px, 4vw, 40px);
  --radius-sm: 10px;
  --radius: 14px;
  --radius-lg: 20px;

  --section-y: clamp(80px, 9vw, 136px);
  --section-y-secondary: clamp(48px, 6vw, 84px);
  --gap-block: clamp(32px, 4vw, 48px);
  --gap-grid: clamp(14px, 1.6vw, 22px);

  /* One source of truth for the header: its own height and the anchor offset
     derive from it, so a link can never land a section under the bar. */
  --nav-h: 76px;

  --z-base: 1;
  --z-raised: 5;
  --z-float: 7;
  --z-sticky: 50;
  --z-nav: 100;
  --z-skip: 200;

  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --shadow-card: 0 18px 40px -28px rgba(0, 0, 0, 0.9);
  --shadow-lift: 0 40px 80px -48px rgba(0, 0, 0, 1);
}

/* ----------------------------------------------------------------- fonts -- */
/* Self-hosted, not Google-hosted. The <link rel="stylesheet"> to
   fonts.googleapis.com was render-blocking: a warm local server measured a
   median FCP of 1940ms (worst run 3000ms) on a page whose own DOM is
   interactive at 85ms. The entire perceived load was one third-party request.
   Serving the files from this origin removes both the DNS/TLS round trip and
   the blocking CSS, and lets the woff2 arrive alongside the stylesheet that
   asks for it, so there is almost nothing left to swap.
   Latin and latin-ext only — the page ships English and Spanish, and the
   Cyrillic / Greek / Vietnamese subsets Google also serves would be dead
   weight. Both families are variable, so one file covers every weight. */
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('../fonts/sora-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('../fonts/sora-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500 700;
  font-display: swap;
  src: url('../fonts/jetbrains-mono-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500 700;
  font-display: swap;
  src: url('../fonts/jetbrains-mono-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ------------------------------------------------------------------ base -- */
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
  scroll-padding-top: calc(var(--nav-h) + 24px);
}

section[id] {
  scroll-margin-top: calc(var(--nav-h) + 24px);
}

body {
  font-family: var(--body);
  background: var(--bg);
  color: var(--text);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
  letter-spacing: 0;
  font-feature-settings: 'ss01', 'cv11';
  /* Same overhead depth the app paints on its own body — never a flat plane. */
  background-image: radial-gradient(1100px 700px at 50% -12%, rgb(var(--surface-rgb) / 0.035), transparent 60%);
  background-repeat: no-repeat;
}

main {
  position: relative;
  /* clip, not hidden: `overflow: hidden` makes an element a scroll container,
     so every view() timeline inside resolved against <main> — which never
     scrolls — and every scroll-driven animation sat frozen at one end of its
     range. `clip` does the same visual job without creating a scrollport. */
  overflow: clip;
  background:
    radial-gradient(1050px 620px at 72% 4%, rgb(var(--accent-rgb) / 0.075), transparent 68%),
    radial-gradient(920px 540px at 18% 32%, rgb(var(--accent-rgb) / 0.045), transparent 72%),
    radial-gradient(760px 520px at 86% 68%, rgb(var(--accent-rgb) / 0.035), transparent 74%),
    var(--bg);
}
main > section {
  position: relative;
  z-index: var(--z-base);
}

h1, h2, h3, h4, h5 { font-family: var(--display); line-height: 1.12; letter-spacing: 0; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; }
img, svg { display: block; max-width: 100%; }
/* The <use> sprite is a definitions block, never a rendered box. */
.icon-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
ul, ol { list-style: none; }
::selection { background: var(--accent); color: #14090a; }

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

/* Set by landing.js while the mobile menu sheet is open. */
body.nav-locked {
  position: fixed;
  left: 0;
  right: 0;
  width: 100%;
  overflow: hidden;
}

.skip-link {
  position: fixed;
  top: 12px;
  left: 12px;
  z-index: var(--z-skip);
  padding: 12px 18px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  border: 1px solid var(--line-strong);
  font-family: var(--display);
  font-weight: 600;
  font-size: 14px;
  transform: translateY(-220%);
}
.skip-link:focus-visible { transform: none; }

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

/* ---------------------------------------------------------------- layout -- */
.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin: 0 auto;
  padding-inline: var(--gutter);
}

.section-pad { padding-block: var(--section-y); }
.section-pad-secondary { padding-block: var(--section-y-secondary); }

/* Section head — the page's spine.
   One rule spanning the full container, headline on the left half, lead on the
   right. Every section starts on the same horizontal beat, and the 1200px
   measure is actually used instead of leaving the right half empty. The old
   42px orange dash is gone: an ornament repeated six times is grammar nobody
   chose, and the accent is worth more spent on the one thing per section that
   matters. */
.section-head {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 4fr);
  gap: clamp(20px, 3.4vw, 56px);
  align-items: start;
  margin-bottom: var(--gap-block);
  padding-top: clamp(20px, 2.2vw, 30px);
  border-top: 1px solid var(--line);
}
.section-head h2 {
  font-size: clamp(28px, 3.6vw, 46px);
  font-weight: 700;
  text-wrap: balance;
}
/* The setup takes its own line and the payoff takes the next one. Run inline,
   the two sentences collide mid-line and the grey-to-white turn — which is the
   whole device — reads as a stray colour change instead of a beat. */
.section-head h2 .dim {
  display: block;
  color: var(--muted);
}
.section-head p {
  /* Optical, not mathematical: lifts the lead's first line onto roughly the
     same baseline as the headline's, which top-alignment alone never gives. */
  padding-top: 0.5em;
  color: var(--muted);
  font-size: clamp(15.5px, 1.3vw, 17.5px);
  max-width: 42ch;
  text-wrap: pretty;
}

/* Below the split the two halves stop being a pair and become a stack. */
@media (max-width: 900px) {
  .section-head {
    grid-template-columns: minmax(0, 1fr);
    gap: 14px;
  }
  .section-head h2 { max-width: 20ch; }
  .section-head p { padding-top: 0; max-width: 56ch; }
}

/* ---------------------------------------------------------------- buttons - */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  min-height: 48px;
  padding: 13px 24px;
  border: 0;
  border-radius: 12px;
  background: none;
  cursor: pointer;
  font-family: var(--display);
  font-size: 15px;
  font-weight: 600;
  white-space: nowrap;
  transition: transform 0.25s var(--ease), background 0.25s var(--ease),
              border-color 0.25s var(--ease), color 0.25s var(--ease);
}
.btn svg { width: 17px; height: 17px; transition: transform 0.3s var(--ease); }

.btn-primary {
  background: linear-gradient(180deg, var(--accent-soft), var(--accent) 55%, var(--accent-deep));
  color: #1c0d00;
}
.btn-primary:hover { transform: translateY(-1px); }
.btn-primary:hover svg { transform: translateX(3px); }
.btn-primary:active { transform: translateY(0); }

.btn-ghost {
  background: rgba(255, 255, 255, 0.035);
  border: 1px solid var(--line-strong);
  color: var(--text);
}
.btn-ghost:hover { background: rgba(255, 255, 255, 0.07); border-color: rgba(255, 255, 255, 0.2); }

.btn-sm { min-height: 44px; padding: 11px 18px; font-size: 14px; }

/* -------------------------------------------------------------------- nav - */
.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: var(--z-nav);
  transition: background 0.3s var(--ease), border-color 0.3s var(--ease);
  border-bottom: 1px solid transparent;
}
.nav.scrolled {
  background: rgba(7, 7, 10, 0.78);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom-color: var(--line);
}

/* Fixed height, never content-driven: nothing inside can resize the bar.
   Three tracks rather than one flex row: the brand and the actions each own an
   equal side track, so the links land on the page's centre line instead of
   wherever the wordmark happens to end. The side tracks are `1fr` and the
   centre is `auto`, which keeps the menu centred even when a translation makes
   one side wider than the other — "Preguntas frecuentes" is 40px wider than
   "FAQ" and the row must not drift. */
.nav-inner {
  width: 100%;
  max-width: var(--maxw);
  margin: 0 auto;
  height: var(--nav-h);
  padding: 0 var(--gutter);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 24px;
}

.brand { display: inline-flex; align-items: center; gap: 10px; flex-shrink: 0; }
.brand-mark { display: grid; place-items: center; width: 30px; height: 30px; }
.brand-mark img { width: 100%; height: 100%; object-fit: contain; }
.brand-name {
  font-family: var(--display);
  font-weight: 700;
  font-size: 19px;
  letter-spacing: 0;
}
.brand-name b { color: var(--accent); font-weight: 700; }

/* Three zones: the brand anchors the left, the menu holds the centre and the
   account actions close the right. The gaps either side of the menu are the
   separators — nothing else needs a rule or a divider. */
.nav-links { display: flex; align-items: center; justify-content: center; gap: 4px; justify-self: center; }
.nav-links > a {
  position: relative;
  padding: 8px 11px;
  border-radius: 9px;
  color: var(--muted);
  font-family: var(--display);
  font-size: 13.5px;
  font-weight: 500;
  transition: color 0.2s, background 0.2s;
}
.nav-links > a:hover { color: var(--text); background: rgba(255, 255, 255, 0.045); }
.nav-links > a[aria-current="true"] { color: var(--text); }
/* The active marker is taken out of flow on purpose. As an in-flow ::after it
   made the current link 7px taller, which resized the whole header every time
   the scrollspy moved between sections — the header appeared to jump. */
.nav-links > a[aria-current="true"]::after {
  content: "";
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 3px;
  height: 2px;
  border-radius: 2px;
  background: var(--accent);
}
.nav-divider,
.nav-mobile-login,
.nav-mobile-cta { display: none; }

.nav-actions { display: flex; align-items: center; justify-content: flex-end; gap: 10px; justify-self: end; flex-shrink: 0; }

.lang-toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--line);
  width: 90px;
  height: 42px;
  flex-shrink: 0;
}
.lang-btn {
  position: relative;
  z-index: 2;
  width: 40px;
  min-height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 0;
  border-radius: 7px;
  cursor: pointer;
  color: var(--muted);
  font-family: var(--display);
  font-size: 11.5px;
  font-weight: 700;
  transition: color 0.2s;
}
.lang-btn[aria-pressed="true"] { color: var(--text); }
.lang-btn:hover[aria-pressed="false"] { color: rgba(255, 255, 255, 0.85); }
.lang-slider {
  position: absolute;
  z-index: 1;
  top: 3px;
  left: 3px;
  width: 40px;
  height: calc(100% - 6px);
  border-radius: 7px;
  background: rgba(255, 255, 255, 0.09);
  border: 1px solid var(--line);
  transition: transform 0.25s var(--ease);
  pointer-events: none;
}
.lang-toggle.is-es .lang-slider { transform: translateX(42px); }

.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
  width: 44px;
  height: 44px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--line);
  border-radius: 10px;
  cursor: pointer;
}
.nav-toggle span {
  display: block;
  width: 17px;
  height: 1.5px;
  border-radius: 2px;
  background: var(--text);
  transition: transform 0.3s var(--ease), opacity 0.2s var(--ease);
}
.nav.menu-open .nav-toggle span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
.nav.menu-open .nav-toggle span:nth-child(2) { opacity: 0; }
.nav.menu-open .nav-toggle span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }


/* ------------------------------------------------------------------ hero -- */
.hero {
  position: relative;
  padding-top: clamp(112px, 15vh, 168px);
  padding-bottom: clamp(56px, 7vw, 96px);
  overflow: visible;
}
.hero::before {
  content: "";
  position: absolute;
  inset: -10% -20% auto -20%;
  height: 720px;
  background:
    radial-gradient(52% 48% at 62% 34%, rgb(var(--accent-rgb) / 0.13), transparent 70%),
    radial-gradient(46% 40% at 22% 18%, rgb(var(--accent-rgb) / 0.06), transparent 72%);
  pointer-events: none;
}

.hero-inner {
  position: relative;
  z-index: var(--z-base);
  display: grid;
  grid-template-columns: minmax(0, 0.86fr) minmax(0, 1.14fr);
  grid-template-areas:
    "copy stage"
    "apps stage";
  gap: 0 clamp(36px, 5vw, 68px);
  align-items: center;
}
.hero-copy {
  grid-area: copy;
}
.hero-stage {
  grid-area: stage;
  grid-row: 1 / 3;
}
.hero-apps {
  grid-area: apps;
  margin-top: 24px;
}

/* A tinted pill with a coloured dot is the single most recognisable tell of a
   generated landing page, and it was the first thing above the headline. The
   information it carries is worth keeping — it names who this is for — so only
   the badge goes.
   What replaces it is already this page's own grammar: the label-plus-hairline
   the split section uses for its two tiers. The hero now opens on the same
   horizontal beat as every section below it, instead of on a widget. */
.eyebrow {
  display: flex;
  align-items: center;
  gap: 16px;
  color: var(--accent);
  font-family: var(--display);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.eyebrow::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, var(--accent-line), rgb(var(--accent-rgb) / 0));
}

.hero h1 {
  margin-top: 18px;
  font-size: clamp(36px, 4.6vw, 62px);
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: 0;
  max-width: 16ch;
}
/* one sentence per line: the contrast is the headline's whole job */
.hero h1 span { display: block; text-wrap: balance; }
.hero h1 .accent { color: var(--accent); }

.hero-sub {
  margin-top: 18px;
  max-width: 48ch;
  color: var(--muted);
  font-size: clamp(16px, 1.25vw, 18px);
  line-height: 1.55;
  text-wrap: pretty;
}

.hero-cta { display: flex; flex-wrap: wrap; gap: 16px; margin-top: 28px; }

/* Store badges. Apple ships its artwork tight to the mark; Google bakes ~10%
   transparent padding into its PNG, so matching the two on height alone leaves
   Google visibly smaller. Google is scaled up by that ratio and pulled back
   with negative margins, which aligns the marks rather than the files. */
.hero-apps-note {
  color: var(--faint);
  font-family: var(--display);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.hero-apps-badges {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 16px;
  margin-top: 10px;
}
/* The badge is a real destination now, so it behaves like one: a pointer, a
   focus ring, and Apple's own artwork left alone apart from lifting the whole
   lockup a little. Apple's guidelines forbid recolouring or reshaping it. */
.hero-apps-link {
  display: inline-flex;
  border-radius: 8px;
  transition: transform 0.2s var(--ease), opacity 0.2s var(--ease);
}
.hero-apps-link:hover { transform: translateY(-1px); opacity: 0.9; }
.hero-apps-link:focus-visible {
  outline: 2px solid rgb(var(--accent-rgb) / 0.9);
  outline-offset: 4px;
}
.final-store { align-self: flex-start; }
.badge {
  width: auto;
  user-select: none;
}
.badge-apple { height: 42px; }

/* The three trust chips lived here. They repeated the compatibility strip
   directly below, so the hero ends on the microcopy and some quiet space. */

/* ============================================================ hero visual --
   Three phones: one protagonist flanked by two real, different screens.
   --pw drives everything (widths, offsets and the UI type scale), so the
   composition rescales as one object instead of drifting apart.
   -------------------------------------------------------------------------- */
.hero-stage {
  position: relative;
  --hx: 0;
  --hy: 0;
  padding: 24px 32px;
  overflow: visible;
}

.phone-cluster {
  --pw: 268px;   /* protagonist width      */
  --sp: 0.44;    /* side push, in --pw     */
  --ss: 0.78;    /* side scale             */
  --st: 7deg;    /* side tilt              */
  position: relative;
  height: calc(var(--pw) * 2.19);
  margin-inline: auto;
  max-width: 100%;
  perspective: 1200px;
}

.phone-cluster .phone {
  position: absolute;
  top: 0;
  left: 50%;
  width: var(--pw);
  transition: transform 0.5s var(--ease), box-shadow 0.3s var(--ease), z-index 0.3s var(--ease);
}

.phone-main {
  z-index: 3;
  transform: translate3d(calc(-50% + var(--hx) * 8px), calc(var(--hy) * 5px), 0);
  box-shadow: 0 30px 70px -15px rgba(0, 0, 0, 0.85), 0 0 50px rgb(var(--accent-rgb) / 0.08), inset 0 1px 0 rgb(var(--surface-rgb) / 0.12);
}
.phone-side {
  z-index: 1;
  box-shadow: 0 15px 35px -10px rgba(0, 0, 0, 0.9);
}
/* Depth, not obscurity: the sides stay readable as distinct screens. */
.phone-side .phone-screen {
  filter: brightness(0.8) saturate(0.85) blur(0.6px);
  transition: filter 0.3s var(--ease);
}
.phone-side::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(180deg, rgba(7, 7, 10, 0.08), rgba(7, 7, 10, 0.28));
  pointer-events: none;
}
.phone-l {
  transform:
    translate3d(calc(-50% - var(--pw) * var(--sp) + var(--hx) * -12px), calc(var(--pw) * 0.05 + var(--hy) * -7px), 0)
    rotate(calc(var(--st) * -1)) scale(var(--ss));
}
.phone-r {
  transform:
    translate3d(calc(-50% + var(--pw) * var(--sp) + var(--hx) * 12px), calc(var(--pw) * 0.05 + var(--hy) * 7px), 0)
    rotate(var(--st)) scale(var(--ss));
}

@media (hover: hover) and (pointer: fine) {
  .phone-l:hover {
    transform:
      translate3d(calc(-50% - var(--pw) * var(--sp) + var(--hx) * -12px), calc(var(--pw) * 0.02 + var(--hy) * -7px), 10px)
      rotate(calc(var(--st) * -0.5)) scale(calc(var(--ss) * 1.06));
    z-index: 4;
  }
  .phone-r:hover {
    transform:
      translate3d(calc(-50% + var(--pw) * var(--sp) + var(--hx) * 12px), calc(var(--pw) * 0.02 + var(--hy) * 7px), 10px)
      rotate(calc(var(--st) * 0.5)) scale(calc(var(--ss) * 1.06));
    z-index: 4;
  }
  .phone-side:hover .phone-screen {
    filter: brightness(0.92) saturate(0.96) blur(0px);
  }
}

/* Entrance: the centre rises, the sides arrive from their own side.
   Keyframes with fill-mode backwards animate FROM the offset TO each phone's
   own resting transform, so the final position never depends on a runtime
   class — and the cursor parallax keeps working once the animation ends. */
@keyframes phone-in-main {
  from { opacity: 0; transform: translate3d(-50%, 30px, 0); }
}
@keyframes phone-in-left {
  from {
    opacity: 0;
    transform: translate3d(calc(-50% - var(--pw) * 0.98), calc(var(--pw) * 0.05), 0) rotate(calc(var(--st) * -1.8)) scale(var(--ss));
  }
}
@keyframes phone-in-right {
  from {
    opacity: 0;
    transform: translate3d(calc(-50% + var(--pw) * 0.98), calc(var(--pw) * 0.05), 0) rotate(calc(var(--st) * 1.8)) scale(var(--ss));
  }
}
/* The three phones arriving was a beginning without an end. The hero's job is
   to say the league is live right now, and every number that proves it is
   already on screen sitting still: an open vote at 75%, a payment two hours
   old, a ballot closing in three. So the entrance finishes the sentence —
   phones, then the vote filling, then the two callouts, then the countdown.
   Nothing here is invented; the motion only makes the static figures true. */
@keyframes bar-fill {
  from { transform: scaleX(0); }
}
@keyframes callout-pop {
  from { opacity: 0; transform: translateY(12px) scale(0.94); filter: blur(5px); }
}

/* The animation is opt-in: landing.js adds .animate inside a rAF once the
   first frame is on screen. Resting position is therefore the default, so a
   throttled tab, a failed script or reduced motion all leave the phones
   correctly placed instead of stranding them mid-entrance. */
@media (prefers-reduced-motion: no-preference) {
  .hero-stage.animate .phone-main { animation: phone-in-main 0.7s var(--ease) backwards; }
  .hero-stage.animate .phone-l { animation: phone-in-left 0.78s var(--ease) 0.08s backwards; }
  .hero-stage.animate .phone-r { animation: phone-in-right 0.78s var(--ease) 0.16s backwards; }

  /* Scale, not width: the fills keep their authored percentage as an inline
     width and only the transform moves, so nothing reflows mid-sequence. */
  .hero-stage.animate .phone-main .ph-opt i {
    transform-origin: left center;
    animation: bar-fill 0.72s var(--ease) 0.55s backwards;
  }
  .hero-stage.animate .callout-pay { animation: callout-pop 0.5s var(--ease) 0.7s backwards; }
  .hero-stage.animate .callout-vote { animation: callout-pop 0.5s var(--ease) 0.84s backwards; }
  .hero-stage.animate .callout-bar i {
    transform-origin: left center;
    animation: bar-fill 0.8s var(--ease) 1s backwards;
  }
}

/* On a phone the single screen is statically placed, so the desktop keyframes —
   which animate from a -50% translate — would drop it half a width to the left
   for the length of the entrance. It rises instead. */
@keyframes phone-in-solo {
  from { opacity: 0; transform: translate3d(0, 26px, 0); }
}
/* Callouts sit in the gaps around the cluster — never over the centre phone. */
.callout-pay { top: 2px; right: 8px; }
.callout-vote { bottom: 6px; left: 8px; }
.callout-bar {
  margin-top: 8px;
  height: 3px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.1);
  overflow: hidden;
}
.callout-bar i { display: block; height: 100%; width: 75%; background: var(--app-warning); border-radius: 3px; }

/* Every surface in the app catches a little light on its top edge. */
.ph-card,
.step { box-shadow: inset 0 1px 0 rgb(var(--surface-rgb) / 0.05); }
/* active vote */
.vote-q { font-size: 13.5px; font-weight: 600; line-height: 1.4; }
.vote-bar {
  margin-top: 11px;
  height: 6px;
  border-radius: 6px;
  background: rgb(var(--surface-rgb) / 0.08);
  overflow: hidden;
}
.vote-bar i { display: block; height: 100%; border-radius: 6px; background: var(--app-warning); }
.vote-opts { display: flex; gap: 7px; margin-top: 11px; }
.vote-opt {
  flex: 1;
  padding: 8px 10px;
  border-radius: 9px;
  border: 1px solid var(--app-border);
  background: rgb(var(--surface-rgb) / 0.02);
  text-align: center;
  font-size: 12px;
  color: var(--app-text-2);
}
.vote-opt.on { border-color: rgba(45, 190, 132, 0.4); background: rgba(45, 190, 132, 0.12); color: var(--app-text); }

/* rules list */
.rule-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid var(--app-border);
  font-size: 12.5px;
}
.rule-row:last-child { border-bottom: 0; padding-bottom: 0; }
.rule-row:first-child { padding-top: 0; }
.rule-t { flex: 1; min-width: 0; }
.rule-t b { display: block; font-weight: 600; font-size: 12.5px; }
.rule-t span { display: block; color: var(--app-text-3); font-size: 11px; }
.rule-amt { font-family: var(--display); font-weight: 700; font-size: 13px; color: var(--accent); }

/* activity feed */
.act-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid var(--app-border);
}
.act-row:last-child { border-bottom: 0; padding-bottom: 0; }
.act-row:first-child { padding-top: 0; }
/* ------------------------------------------------------------------ phone --
   The screens below reproduce the real Fantasy Hall app: same tokens, same
   component anatomy (see the app repo's tailwind.config.js and index.css).
   Type is em-based off --pw so a phone stays internally consistent at any size.
   -------------------------------------------------------------------------- */
/* Shell, rail, radius and buttons all come from the shared iPhone recipe
   further down, off the --pw the cluster sets. */
.phone-screen {
  display: flex;
  flex-direction: column;
  background: var(--app-bg);
  aspect-ratio: 9 / 19.5;
  font-size: max(10.5px, calc(var(--pw, 288px) / 18.5));
  line-height: 1.4;
  color: var(--app-text);
}

/* header — back, brand, breadcrumb, contextual title */
.ph-head {
  display: flex;
  align-items: center;
  gap: 0.55em;
  padding: 0.85em 0.8em;
  border-bottom: 1px solid var(--app-border);
  background: rgba(14, 17, 23, 0.85);
}
.ph-back { color: var(--app-text-2); font-size: 1.05em; line-height: 1; flex-shrink: 0; }
.ph-head-t { min-width: 0; flex: 1; }
.ph-head-logo { width: 1.3em; height: 1.3em; flex-shrink: 0; }
.ph-head-logo img { width: 100%; height: 100%; object-fit: contain; }
.ph-crumb { color: var(--app-text-3); font-size: 0.72em; line-height: 1.3; }
.ph-crumb b { color: var(--app-primary); font-weight: 600; }
.ph-h1 {
  display: block;
  font-family: var(--display);
  font-size: 0.95em;
  font-weight: 600;
  letter-spacing: 0;
  line-height: 1.25;
}
.ph-h1 small { display: block; color: var(--app-text-2); font-size: 0.72em; font-weight: 400; }

/* body */
.ph-body {
  flex: 1;
  min-height: 0;
  padding: 0.8em;
  display: grid;
  gap: 0.6em;
  align-content: start;
}
.ph-body.with-tabs { padding-bottom: 4.2em; }

.ph-eyebrow {
  font-size: 0.66em;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--app-primary);
}
.ph-display {
  font-family: var(--display);
  font-size: 1.4em;
  font-weight: 700;
  letter-spacing: 0;
  line-height: 1.15;
  margin-top: 0.15em;
}
.ph-display b { color: var(--app-primary); font-weight: 700; }
.ph-lead { color: var(--app-text-2); font-size: 0.78em; margin-top: 0.3em; }

/* card — bg-surface border border-border rounded-2xl */
.ph-card {
  padding: 0.75em;
  border-radius: 0.9em;
  border: 1px solid var(--app-border);
  background: var(--app-surface);
}
.ph-card.is-primary {
  border-color: rgb(var(--accent-rgb) / 0.4);
  box-shadow: 0 0 18px rgb(var(--accent-rgb) / 0.1);
}
.ph-card-h {
  display: flex;
  align-items: center;
  gap: 0.5em;
  margin-bottom: 0.6em;
}
.ph-chip {
  display: grid;
  place-items: center;
  width: 1.7em;
  height: 1.7em;
  border-radius: 0.5em;
  flex-shrink: 0;
  background: rgb(var(--accent-rgb) / 0.1);
  border: 1px solid rgb(var(--accent-rgb) / 0.2);
  color: var(--app-primary);
}
.ph-chip svg { width: 0.95em; height: 0.95em; }
.ph-card-t { font-family: var(--display); font-size: 0.82em; font-weight: 600; letter-spacing: 0; }

.ph-l { display: block; color: var(--app-text-2); font-size: 0.74em; font-weight: 500; }
.ph-l-pri { color: var(--app-primary); }
.ph-money {
  display: block;
  margin-top: 0.2em;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 1.55em;
  font-weight: 700;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}
.ph-money.ok { color: var(--app-emerald); }
.ph-money.pri { color: var(--app-primary); }

/* status-pill */
.ph-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
  margin-top: 0.5em;
  padding: 0.3em 0.7em;
  border-radius: 999px;
  border: 1px solid transparent;
  font-size: 0.7em;
  font-weight: 600;
}
.ph-pill::before { content: ""; width: 0.42em; height: 0.42em; border-radius: 50%; background: currentColor; }
.ph-pill.is-paid { color: var(--app-emerald); background: rgba(45, 190, 132, 0.12); border-color: rgba(45, 190, 132, 0.3); }
.ph-pill.is-late { color: var(--app-danger); background: rgba(242, 84, 91, 0.12); border-color: rgba(242, 84, 91, 0.3); }

.ph-cta {
  margin-top: 0.6em;
  padding: 0.55em;
  border-radius: 0.6em;
  background: var(--app-primary);
  color: #1a0e05;
  text-align: center;
  font-family: var(--display);
  font-size: 0.78em;
  font-weight: 600;
}
.ph-name { display: block; margin-top: 0.2em; font-family: var(--display); font-size: 0.95em; font-weight: 600; }

/* poll options with result fill */
.ph-q { font-size: 0.8em; font-weight: 500; line-height: 1.35; }
.ph-opts { display: grid; gap: 0.35em; margin-top: 0.6em; }
.ph-opt {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5em;
  padding: 0.45em 0.6em;
  border-radius: 0.5em;
  border: 1px solid var(--app-border);
  background: var(--app-surface-hi);
  font-size: 0.74em;
  overflow: hidden;
}
.ph-opt i {
  position: absolute;
  inset: 0 auto 0 0;
  background: rgb(var(--accent-rgb) / 0.1);
}
.ph-opt span { position: relative; }
.ph-opt b { position: relative; font-weight: 600; color: var(--app-text-2); }
.ph-opt.lead { border-color: rgba(45, 190, 132, 0.4); background: rgba(45, 190, 132, 0.12); }
.ph-opt.lead i { background: rgba(45, 190, 132, 0.15); }
.ph-opt.lead b { color: var(--app-emerald); }
.ph-foot { margin-top: 0.6em; text-align: center; color: var(--app-text-3); font-size: 0.68em; }

/* activity feed with connector rail */
.ph-act { position: relative; display: flex; gap: 0.55em; padding-bottom: 0.7em; }
.ph-act:last-child { padding-bottom: 0; }
.ph-act:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 0.82em;
  top: 1.9em;
  bottom: 0.05em;
  width: 1px;
  background: var(--app-border);
}
.ph-act-ic {
  display: grid;
  place-items: center;
  width: 1.65em;
  height: 1.65em;
  border-radius: 0.45em;
  flex-shrink: 0;
  border: 1px solid var(--app-border);
}
.ph-act-ic svg { width: 0.85em; height: 0.85em; }
.ph-act-ic.pay { background: rgba(45, 190, 132, 0.12); color: var(--app-emerald); }
.ph-act-ic.fine { background: rgba(242, 84, 91, 0.12); color: var(--app-danger); }
.ph-act-t { font-size: 0.74em; line-height: 1.3; }
.ph-act-t b { font-weight: 600; }
.ph-act-t span { color: var(--app-text-2); }
.ph-act-t em { display: block; font-style: normal; color: var(--app-text-3); font-size: 0.88em; margin-top: 0.15em; }

/* members balance rows */
.ph-mem {
  display: flex;
  align-items: center;
  gap: 0.5em;
  padding: 0.45em 0;
  border-top: 1px solid var(--app-border);
  font-size: 0.74em;
}
.ph-mem:first-of-type { border-top: 0; }
.ph-av {
  width: 1.5em;
  height: 1.5em;
  border-radius: 50%;
  display: grid;
  place-items: center;
  flex-shrink: 0;
  font-family: var(--display);
  font-size: 0.62em;
  font-weight: 700;
  color: #0b0e13;
}
.ph-mem-n { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ph-mem-v { font-family: 'JetBrains Mono', ui-monospace, monospace; font-weight: 700; }
.ph-mem-v.ok { color: var(--app-emerald); }
.ph-mem-v.due { color: var(--app-danger); }

/* tribunal bubbles */
.ph-bubble { display: flex; gap: 0.5em; margin-bottom: 0.6em; }
.ph-bubble.mine { flex-direction: row-reverse; }
.ph-bubble-ic {
  display: grid;
  place-items: center;
  width: 1.8em;
  height: 1.8em;
  border-radius: 0.55em;
  flex-shrink: 0;
  background: rgb(var(--accent-rgb) / 0.1);
  border: 1px solid rgb(var(--accent-rgb) / 0.3);
  color: var(--app-primary);
}
.ph-bubble-ic svg { width: 0.9em; height: 0.9em; }
.ph-bubble.mine .ph-bubble-ic {
  background: var(--app-surface-hi);
  border-color: var(--app-border);
  color: var(--app-text-2);
}
.ph-bubble-b {
  padding: 0.6em 0.7em;
  border-radius: 0.85em;
  border: 1px solid var(--app-border);
  background: var(--app-surface);
  font-size: 0.74em;
  line-height: 1.4;
  border-top-left-radius: 0.3em;
}
.ph-bubble.mine .ph-bubble-b {
  background: var(--app-primary);
  border-color: var(--app-primary);
  color: #1a0e05;
  border-top-left-radius: 0.85em;
  border-top-right-radius: 0.3em;
}
.ph-bubble-l { display: block; font-size: 0.86em; font-weight: 500; color: var(--app-primary); margin-bottom: 0.35em; }
.ph-bubble.mine .ph-bubble-l { color: rgba(26, 14, 5, 0.7); }
.ph-bubble-time { display: block; text-align: right; margin-top: 0.35em; font-size: 0.8em; color: var(--app-text-3); }
.ph-bubble.mine .ph-bubble-time { color: rgba(26, 14, 5, 0.6); }
.ph-input {
  display: flex;
  align-items: center;
  gap: 0.4em;
  margin-top: auto;
  padding: 0.5em 0.65em;
  border-radius: 0.6em;
  border: 1px solid var(--app-border);
  background: var(--app-surface-hi);
  color: var(--app-text-3);
  font-size: 0.72em;
}
.ph-input-send {
  margin-left: auto;
  display: grid;
  place-items: center;
  width: 1.5em;
  height: 1.5em;
  border-radius: 0.4em;
  background: var(--app-primary);
  color: #1a0e05;
  flex-shrink: 0;
}
.ph-input-send svg { width: 0.8em; height: 0.8em; }

/* bottom tab bar — 5 labelled tabs, active pill */
.ph-tabs {
  position: absolute;
  inset: auto 0 0 0;
  display: flex;
  justify-content: space-around;
  gap: 0.1em;
  /* The extra bottom padding parks the bar above the home indicator the device
     draws, exactly as iOS does — otherwise the bar strikes through the labels. */
  padding: 0.45em 0.35em calc(var(--pw, 288px) * 0.055);
  border-top: 1px solid var(--app-border);
  background: rgba(22, 27, 34, 0.95);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.ph-tab {
  position: relative;
  flex: 1;
  display: grid;
  gap: 0.15em;
  justify-items: center;
  padding: 0.3em 0.1em;
  border-radius: 0.55em;
  color: var(--app-text-2);
  font-size: 0.62em;
  font-weight: 500;
}
.ph-tab svg { width: 1.35em; height: 1.35em; }
.ph-tab.on {
  color: var(--app-primary);
  font-weight: 600;
  background: rgb(var(--accent-rgb) / 0.1);
  border: 1px solid rgb(var(--accent-rgb) / 0.2);
}

/* ---------------------------------------------------------------- split --- */
/* The division of labour, shown rather than argued: what the fantasy app you
   already use keeps, and what Fantasy Hall picks up. */
.split,
.how {
  position: relative;
  border-block: 0;
  background:
    radial-gradient(760px 360px at 82% 0%, rgb(var(--accent-rgb) / 0.055), transparent 72%),
    linear-gradient(180deg, rgba(11, 11, 16, 0) 0%, rgba(11, 11, 16, 0.78) 14%, rgba(11, 11, 16, 0.82) 86%, rgba(11, 11, 16, 0) 100%);
}
.split::after,
.how::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.62;
  background:
    linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.075), transparent) top / 100% 1px no-repeat,
    linear-gradient(90deg, transparent, rgb(var(--accent-rgb) / 0.08), transparent) bottom / 100% 1px no-repeat;
}
.split > .wrap,
.how > .wrap {
  position: relative;
  z-index: var(--z-base);
}

.split-label {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 16px;
  color: var(--faint);
  font-family: var(--display);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.split-label::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.split-label.is-ours { color: var(--accent); }
.split-label.is-ours::after { background: var(--accent-line); }

/* Both tiers are complete rectangles, never a ragged tail. Four items sit on
   four columns; nine sit on three. `auto-fit` used to give the lower tier five
   columns and leave a hole in the last row — the one shape a "look how much
   more you get" grid cannot afford. The wider cells also make the second tier
   read as the bigger system, which is the argument. */
/* Modern Handoff Architecture Layout */
.handoff-stage {
  display: grid;
  grid-template-columns: minmax(0, 0.82fr) auto minmax(0, 1.18fr);
  align-items: stretch;
  gap: 20px;
  margin-top: 42px;
}

.handoff-card {
  display: flex;
  flex-direction: column;
  padding: 32px 28px;
  border-radius: 24px;
  border: 1px solid var(--line);
  background: rgba(14, 14, 20, 0.65);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  transition: border-color 0.3s var(--ease), box-shadow 0.3s var(--ease);
}

.handoff-card.handoff-app {
  background: rgba(12, 12, 18, 0.5);
  border-color: rgba(255, 255, 255, 0.08);
}

.handoff-card.handoff-hall {
  position: relative;
  background: rgba(18, 18, 26, 0.82);
  border-color: rgb(var(--accent-rgb) / 0.28);
  box-shadow: 0 20px 50px -15px rgba(0, 0, 0, 0.8), 0 0 0 1px rgb(var(--accent-rgb) / 0.12);
  overflow: hidden;
}
.handoff-card.handoff-hall::before {
  content: "";
  position: absolute;
  top: -30%;
  right: -20%;
  width: 70%;
  height: 70%;
  background: radial-gradient(circle, rgb(var(--accent-rgb) / 0.15), transparent 70%);
  pointer-events: none;
}

.handoff-card-head {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 24px;
}

.handoff-badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  align-self: flex-start;
  padding: 6px 14px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.02em;
}
.handoff-badge.muted {
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--line);
  color: var(--muted);
}
.handoff-badge.muted svg { width: 15px; height: 15px; color: var(--faint); }
.handoff-badge.highlight {
  background: rgb(var(--accent-rgb) / 0.12);
  border: 1px solid rgb(var(--accent-rgb) / 0.3);
  color: var(--accent);
}
.handoff-badge.highlight svg { width: 15px; height: 15px; color: var(--accent); }
.handoff-badge-logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}
.handoff-badge-logo img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.handoff-subtitle {
  color: var(--faint);
  font-size: 13.5px;
}

/* Connector Bridge */
.handoff-bridge {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding-block: 12px;
}
.bridge-line {
  flex: 1;
  width: 1px;
  background: linear-gradient(180deg, rgba(255,255,255,0.06), rgb(var(--accent-rgb) / 0.4), rgba(255,255,255,0.06));
}
.bridge-pill {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 8px 14px;
  border-radius: 999px;
  background: rgba(20, 20, 28, 0.95);
  border: 1px solid rgb(var(--accent-rgb) / 0.35);
  color: var(--accent);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.6);
  white-space: nowrap;
}
.bridge-logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}
.bridge-logo img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.bridge-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 8px var(--accent);
  animation: pulse-dot 2s infinite;
}
@keyframes pulse-dot {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.4); opacity: 0.6; }
}

/* Pill grids inside cards */
.handoff-pills {
  display: grid;
  gap: 10px;
  list-style: none;
  padding: 0;
  margin: 0;
}
.handoff-pills.theirs {
  grid-template-columns: repeat(2, 1fr);
}
.handoff-pills.ours {
  grid-template-columns: repeat(3, 1fr);
}

.handoff-pills li {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  border-radius: 12px;
  font-size: 14px;
  font-weight: 500;
  transition: all 0.2s var(--ease);
}
.handoff-pills.theirs li {
  background: rgba(255, 255, 255, 0.025);
  border: 1px solid rgba(255, 255, 255, 0.05);
  color: var(--muted);
}
.handoff-pills.ours li {
  background: rgba(255, 122, 26, 0.05);
  border: 1px solid rgb(var(--accent-rgb) / 0.16);
  color: var(--text);
}
.handoff-pills.ours li:hover {
  background: rgba(255, 122, 26, 0.1);
  border-color: rgb(var(--accent-rgb) / 0.35);
  transform: translateY(-1px);
}
.handoff-ic {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 8px;
  flex-shrink: 0;
}
.handoff-pills.theirs .handoff-ic {
  background: rgba(255, 255, 255, 0.05);
  color: var(--faint);
}
.handoff-pills.ours .handoff-ic {
  background: rgb(var(--accent-rgb) / 0.12);
  color: var(--accent);
}
.handoff-ic svg { width: 16px; height: 16px; }

/* Grouped view in Fantasy Hall card */
.handoff-groups {
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.handoff-group {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.group-title {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--accent);
  opacity: 0.85;
}

/* The handoff happens instead of being depicted: the rule draws out from the
   centre, then the arrow drops onto it. One shot on entry, never a loop — a
   looping pulse would be decoration, and decoration would have to be stopped
   offscreen anyway. */
@keyframes flow-rule-draw {
  from { transform: scaleX(0); }
}
@keyframes flow-arrow-drop {
  from { opacity: 0; transform: translateY(-11px) scale(0.86); }
}

/* -------------------------------------------------------------- problem --- */
/* Three things on one band. This is the only section on the page with no product
   in it, so its shape has to do the work the screenshots do elsewhere: a rule
   across the container, an accent tick where each column meets it, and hairlines
   between the columns. Nothing in here is clickable, so nothing in here gets card
   chrome or a hover lift — the boxes were promising an affordance that does not
   exist, and the third one had to hold a hole where its copy ran out. */
.prob-editorial {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0;
  margin-top: clamp(36px, 4.5vw, 56px);
  border-top: 1px solid var(--line);
}

.prob-col {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 26px 34px 4px 0;
}
/* The divider lives on the gap, so the first column stays flush with the
   headline's own left edge and the band lines up with the section spine. It is a
   pseudo-element rather than a border because a border cannot be drawn. */
.prob-col + .prob-col { padding-left: 34px; }
.prob-col + .prob-col::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 1px;
  background: var(--line);
}
/* Where the column meets the rule. Short enough to read as a marker rather than
   as a coloured border. */
.prob-col::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  width: 26px;
  height: 1px;
  background: var(--accent);
}
.prob-col + .prob-col::before { left: 34px; }

/* Sora, not mono: the mono face is reserved for figures inside the mockups,
   where the app sets money that way. 01–03 are labels, not measurements. */
.prob-num {
  display: block;
  margin-bottom: 22px;
  font-family: var(--display);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--accent);
}

.prob-col h3 {
  font-family: var(--display);
  font-size: clamp(19px, 1.6vw, 21px);
  font-weight: 700;
  color: var(--text);
  line-height: 1.25;
  letter-spacing: -0.01em;
  max-width: 20ch;
}

.prob-col p {
  margin-top: 12px;
  color: var(--muted);
  font-size: 15px;
  line-height: 1.55;
}

@media (max-width: 860px) {
  /* Stacked, the divider turns from vertical to horizontal and the tick moves
     with it: same band, read top to bottom. */
  .prob-editorial {
    grid-template-columns: 1fr;
  }
  .prob-col {
    padding: 22px 0 24px;
  }
  .prob-col + .prob-col { padding-left: 0; }
  .prob-col + .prob-col::after {
    top: 0;
    bottom: auto;
    left: 0;
    right: 0;
    width: auto;
    height: 1px;
  }
  .prob-col + .prob-col::before { left: 0; }
  .prob-num { margin-bottom: 14px; }
  .prob-col h3 { max-width: none; }
}

/* ------------------------------------------------------------------ how --- */
/* Four shared rows — marker, title, copy, mini-UI — so the same part of every
   step sits at the same height. Pinning the visual to the bottom with
   `margin-top:auto` used to leave 130px holes in steps 2 and 3, because step 1
   is taller by exactly its CTA. Subgrid moves that slack out of the middle of
   the card and splits it inside the panel, where it reads as air. */
.steps {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-template-rows: auto auto auto auto;
  column-gap: var(--gap-grid);
  row-gap: 0;
}
.step {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  padding: 22px;
  border-radius: var(--radius-lg);
  border: 1px solid var(--line);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.045), rgba(255, 255, 255, 0.012)),
    var(--surface);
  box-shadow: 0 18px 44px -34px rgba(0, 0, 0, 0.95), inset 0 1px 0 rgb(var(--surface-rgb) / 0.06);
  transition: transform 0.3s var(--ease), border-color 0.3s var(--ease), box-shadow 0.3s var(--ease);
}
.step::before {
  content: "";
  position: absolute;
  top: -62px;
  right: -56px;
  z-index: 0;
  width: 180px;
  height: 180px;
  border-radius: 50%;
  background: radial-gradient(circle, rgb(var(--accent-rgb) / 0.24), rgb(var(--accent-rgb) / 0.07) 44%, transparent 70%);
  opacity: 0.48;
  transform: translate3d(22px, -18px, 0);
  transition: opacity 0.35s var(--ease), transform 0.35s var(--ease);
}
.step::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(135deg, rgba(255, 255, 255, 0.08), transparent 34%);
  opacity: 0.42;
}
.step > * {
  position: relative;
  z-index: 1;
}
.step:hover {
  transform: translateY(-2px);
  border-color: rgb(var(--accent-rgb) / 0.24);
  box-shadow: 0 24px 54px -34px rgba(0, 0, 0, 1), 0 0 0 1px rgb(var(--accent-rgb) / 0.04), inset 0 1px 0 rgb(var(--surface-rgb) / 0.08);
}
.step:hover::before {
  opacity: 0.82;
  transform: translate3d(8px, -8px, 0);
}
.step-n {
  display: inline-grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 99px;
  background: var(--accent-tint);
  border: 1px solid var(--accent-line);
  color: var(--accent);
  font-family: var(--display);
  font-size: 13px;
  font-weight: 700;
  box-shadow: 0 0 0 4px rgb(var(--accent-rgb) / 0.045);
}
.step h3 { margin-top: 15px; font-size: 18px; font-weight: 600; text-wrap: balance; }
.step p { margin-top: 8px; margin-bottom: 20px; color: var(--muted); font-size: 14.5px; text-wrap: pretty; }

.step-visual {
  margin-top: auto;
  padding: 13px;
  border-radius: 13px;
  border: 1px solid rgba(255, 255, 255, 0.085);
  background:
    radial-gradient(180px 120px at 100% 0%, rgb(var(--accent-rgb) / 0.08), transparent 68%),
    rgba(7, 7, 10, 0.64);
  box-shadow: inset 0 1px 0 rgb(var(--surface-rgb) / 0.045);
}

@supports (grid-template-rows: subgrid) {
  .step {
    display: grid;
    grid-row: span 4;
    grid-template-rows: subgrid;
  }
  .step-visual {
    margin-top: 0;
    display: grid;
    align-content: center;
  }
}

.mini-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.055);
  font-size: 12.5px;
}
.mini-row:last-of-type { border-bottom: 0; }
.mini-l { color: var(--faint); }
.mini-v { font-family: var(--display); font-weight: 600; }
.mini-cta {
  margin-top: 11px;
  padding: 9px;
  border-radius: 99px;
  background: linear-gradient(180deg, var(--accent-soft), var(--accent));
  color: #1c0d00;
  text-align: center;
  font-family: var(--display);
  font-size: 12px;
  font-weight: 700;
}
.mini-note { margin-top: 11px; color: var(--faint); font-size: 11.5px; }

/* --------------------------------------------------------------- product -- */
/* The shield-monogram grid is a real brand asset lifted from the app's
   public/brand vector patterns; it sits behind the UI and fades out. */
.product-stage { position: relative; }
.product-stage::before {
  content: "";
  position: absolute;
  inset: -6% -4% -10%;
  background: url("../brand/pattern-grid.svg") center / 420px auto repeat;
  opacity: 0.5;
  -webkit-mask-image: radial-gradient(72% 62% at 50% 45%, #000 25%, transparent 78%);
  mask-image: radial-gradient(72% 62% at 50% 45%, #000 25%, transparent 78%);
  pointer-events: none;
}
.product-demo-stage {
  max-width: 1120px;
  margin-inline: auto;
}
/* The shell holds its own shape rather than two clamps that have to agree.
   16:9 is the trailer's native frame, and the cover's backdrop is drawn at the
   same ratio, so card, video and box are one rectangle — nothing is cropped
   and nothing is letterboxed. */
.product-demo-shell {
  position: relative;
  z-index: var(--z-base);
  overflow: hidden;
  aspect-ratio: 16 / 9;
  min-height: 300px;
  max-height: 700px;
  border-radius: var(--radius-lg);
  border: 1px solid rgb(var(--surface-rgb) / 0.12);
  background:
    radial-gradient(720px 420px at 50% 0%, rgb(var(--accent-rgb) / 0.08), transparent 70%),
    #0b0e13;
  box-shadow: var(--shadow-lift), inset 0 1px 0 rgb(var(--surface-rgb) / 0.07);
}
.product-demo-frame {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  /* contain, not cover: a box that ends up a hair off 16:9 should letterbox
     rather than crop the trailer's captions off its own edges. */
  object-fit: contain;
  background: #0b0e13;
  opacity: 0;
  transition: opacity 220ms ease;
}
.product-demo-shell.is-loaded .product-demo-frame {
  opacity: 1;
}
/* A title card rather than a still. The backdrop is the brand sheet, drawn at
   the same 16:9 the shell holds, so it lands 1:1 with no crop — and it is
   1.4KB of SVG, which is why the cover can afford to be decorative. */
.product-demo-cover {
  position: absolute;
  inset: 0;
  z-index: var(--z-raised);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(18px, 2.6vw, 30px);
  width: 100%;
  height: 100%;
  padding: clamp(22px, 3.4vw, 46px);
  color: var(--text);
  text-align: center;
  border: 0;
  background: url("../brand/pattern-lines.svg") center / cover no-repeat, #0e1117;
  cursor: pointer;
  transition: opacity 220ms ease;
}
/* Darkened where the lockup sits and left alone at the edges, so the copy has
   a bed to read on and the brand outlines still come through around it. */
.product-demo-cover::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background:
    radial-gradient(620px 340px at 50% 52%,
      rgba(7, 9, 13, 0.82) 0%,
      rgba(7, 9, 13, 0.5) 58%,
      transparent 82%),
    radial-gradient(700px 400px at 50% 2%, rgb(var(--accent-rgb) / 0.14), transparent 68%),
    linear-gradient(180deg, rgba(7, 9, 13, 0.24) 0%, rgba(7, 9, 13, 0.12) 40%, rgba(7, 9, 13, 0.6) 100%);
  pointer-events: none;
}
.product-demo-lockup,
.product-demo-play {
  position: relative;
  z-index: 1;
}
.product-demo-lockup {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(14px, 1.8vw, 20px);
  min-width: 0;
}
.product-demo-logo {
  display: grid;
  flex: 0 0 auto;
  place-items: center;
  width: clamp(48px, 6vw, 66px);
  height: clamp(48px, 6vw, 66px);
  border-radius: 18px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: rgba(255, 255, 255, 0.06);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
.product-demo-logo img {
  width: 68%;
  height: 68%;
  object-fit: contain;
}
.product-demo-copy {
  display: grid;
  justify-items: center;
  gap: 6px;
  /* A measure for the centred headline. Absolute, not ch: `ch` here resolves
     against the button's 16px, not the display size, and silently squeezed it
     to ~230px. */
  max-width: 480px;
  min-width: 0;
}
.product-demo-kicker {
  color: var(--accent);
  font-size: 0.76rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.product-demo-title {
  color: #fff;
  font-family: var(--display);
  font-size: clamp(1.5rem, 2.8vw, 2.6rem);
  font-weight: 800;
  line-height: 1.04;
  text-wrap: balance;
}
.product-demo-note {
  margin-top: 2px;
  color: rgba(255, 255, 255, 0.72);
  font-size: clamp(0.9rem, 1.15vw, 1rem);
  line-height: 1.5;
}
.product-demo-play {
  display: grid;
  place-items: center;
  width: clamp(58px, 7vw, 76px);
  height: clamp(58px, 7vw, 76px);
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background:
    linear-gradient(135deg, rgba(255, 184, 86, 0.95), rgba(255, 108, 22, 0.95));
  box-shadow: 0 22px 50px -24px rgb(var(--accent-rgb) / 0.95);
  transition: transform 180ms ease, box-shadow 180ms ease;
}
.product-demo-play::before {
  content: "";
  width: 0;
  height: 0;
  margin-left: 5px;
  border-top: 12px solid transparent;
  border-bottom: 12px solid transparent;
  border-left: 18px solid #130b06;
}
.product-demo-cover:hover .product-demo-play,
.product-demo-cover:focus-visible .product-demo-play {
  transform: scale(1.05);
  box-shadow: 0 26px 60px -22px rgb(var(--accent-rgb) / 1);
}
.product-demo-cover:focus-visible {
  outline: 2px solid rgb(var(--accent-rgb) / 0.9);
  outline-offset: -6px;
}
/* The cover fades, then leaves. The author `display: flex` above outranks the
   UA's `[hidden] { display: none }`, so the attribute has to be said again here
   or a dismissed cover keeps its place in the tab order. */
.product-demo-cover[hidden] { display: none; }

/* End card. The trailer's last frame is a logo, so this sits on top of it and
   turns the end of the video into the page's highest-intent moment instead of
   a paused rectangle. */
.product-demo-end {
  position: absolute;
  inset: 0;
  z-index: calc(var(--z-raised) + 2);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(16px, 2.4vw, 26px);
  padding: clamp(22px, 3.4vw, 46px);
  text-align: center;
  background: radial-gradient(620px 340px at 50% 50%,
    rgba(7, 9, 13, 0.92) 0%,
    rgba(7, 9, 13, 0.7) 60%,
    rgba(7, 9, 13, 0.52) 100%);
}
.product-demo-end[hidden] { display: none; }
.product-demo-end-t {
  color: #fff;
  font-family: var(--display);
  font-size: clamp(1.35rem, 2.6vw, 2.4rem);
  font-weight: 800;
  line-height: 1.05;
  text-wrap: balance;
}
.product-demo-end-a {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 10px;
}

.product-demo-loading {
  position: absolute;
  right: clamp(18px, 3vw, 32px);
  bottom: clamp(18px, 3vw, 32px);
  z-index: calc(var(--z-raised) + 1);
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 10px 13px;
  border-radius: 999px;
  color: rgba(255, 255, 255, 0.84);
  font-size: 0.82rem;
  font-weight: 760;
  background: rgba(7, 9, 13, 0.78);
  border: 1px solid rgba(255, 255, 255, 0.12);
  opacity: 0;
  pointer-events: none;
  transition: opacity 180ms ease;
}
.product-demo-spinner {
  width: 15px;
  height: 15px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.22);
  border-top-color: var(--accent);
  animation: demoSpin 850ms linear infinite;
}
.product-demo-shell.is-loading .product-demo-loading {
  opacity: 1;
}
.product-demo-shell.is-loading .product-demo-cover {
  opacity: 0.72;
  pointer-events: none;
}
.product-demo-shell.is-loaded .product-demo-cover,
.product-demo-shell.is-loaded .product-demo-loading {
  opacity: 0;
  pointer-events: none;
}
/* The failure state. It sits above a dimmed poster rather than replacing it,
   so the visitor keeps the context of what they were trying to open. */
.product-demo-error {
  position: absolute;
  inset: auto 0 0 0;
  z-index: calc(var(--z-raised) + 3);
  display: none;
  gap: 10px;
  margin: clamp(18px, 3vw, 34px);
  padding: clamp(18px, 2.4vw, 26px);
  border-radius: var(--radius);
  border: 1px solid var(--line-strong);
  background: rgba(16, 16, 22, 0.94);
  box-shadow: var(--shadow-lift), inset 0 1px 0 rgb(var(--surface-rgb) / 0.06);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  text-align: left;
}
.product-demo-shell.is-error .product-demo-error { display: grid; }
/* Dimmed and inert: the retry button is the one thing to press. */
.product-demo-shell.is-error .product-demo-cover {
  opacity: 0.3;
  pointer-events: none;
}
.product-demo-error-t {
  font-family: var(--display);
  font-size: clamp(16px, 1.5vw, 18px);
  font-weight: 600;
}
.product-demo-error-b {
  max-width: 52ch;
  color: var(--muted);
  font-size: 14.5px;
  text-wrap: pretty;
}
.product-demo-error-a {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 4px;
}

.product-demo-noscript {
  position: absolute;
  left: clamp(20px, 4vw, 44px);
  bottom: clamp(20px, 4vw, 44px);
  z-index: calc(var(--z-raised) + 2);
  margin: 0;
}
.product-demo-noscript a {
  color: #fff;
  font-weight: 800;
}
@keyframes demoSpin {
  to { transform: rotate(360deg); }
}

/* -------------------------------------------------------------- features -- */
.showcase {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.72fr);
  gap: clamp(26px, 4vw, 56px);
  align-items: center;
}
.showcase-nav { display: flex; flex-direction: column; gap: 10px; }
.sc-tab {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: flex-start;
  gap: 14px;
  width: 100%;
  padding: 16px;
  border-radius: var(--radius);
  border: 1px solid rgba(255, 255, 255, 0.045);
  background: rgba(255, 255, 255, 0.012);
  cursor: pointer;
  text-align: left;
  min-height: 48px;
  box-shadow: inset 0 1px 0 rgb(var(--surface-rgb) / 0.025);
  transition: transform 0.25s var(--ease), background 0.25s var(--ease), border-color 0.25s var(--ease), box-shadow 0.25s var(--ease);
}
.sc-tab::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(135deg, rgba(255, 255, 255, 0.05), transparent 42%);
  opacity: 0;
  transition: opacity 0.25s var(--ease);
}
.sc-tab > * {
  position: relative;
  z-index: 1;
}
.sc-tab:hover {
  transform: translateY(-1px);
  background: rgba(255, 255, 255, 0.024);
  border-color: var(--line);
}
.sc-tab:hover::before { opacity: 0.7; }
/* Selected reads through the icon and the surface, not a 3px stripe down the
   side. The chip goes solid accent — the same mark the product uses for the one
   action that matters — so the tab and the screen it drives share a colour. */
.sc-tab[aria-selected="true"] {
  background:
    radial-gradient(280px 150px at 100% 0%, rgb(var(--accent-rgb) / 0.08), transparent 72%),
    linear-gradient(180deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.012)),
    var(--surface);
  border-color: rgb(var(--accent-rgb) / 0.34);
  box-shadow:
    0 18px 42px -34px rgba(0, 0, 0, 0.96),
    inset 0 1px 0 rgb(var(--surface-rgb) / 0.06);
}
.sc-tab-ic {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 11px;
  flex: 0 0 40px;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.055), rgba(255, 255, 255, 0.018)),
    rgba(255, 255, 255, 0.035);
  border: 1px solid var(--line);
  color: var(--muted);
  transition: background 0.25s var(--ease), border-color 0.25s var(--ease), color 0.25s var(--ease), transform 0.25s var(--ease);
}
.sc-tab-ic svg {
  width: 18px;
  height: 18px;
  flex: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.sc-tab[aria-selected="true"] .sc-tab-ic {
  background: linear-gradient(180deg, var(--accent-soft), var(--accent) 60%, var(--accent-deep));
  border-color: transparent;
  color: #1c0d00;
  box-shadow: 0 6px 18px -8px rgb(var(--accent-rgb) / 0.75);
}
.sc-tab:hover .sc-tab-ic { transform: translateY(-1px); }
.sc-tab h3 { font-size: 16.5px; font-weight: 600; }
.sc-tab p { margin-top: 5px; color: var(--muted); font-size: 14px; text-wrap: pretty; }

.sc-tab > span:last-child { min-width: 0; }

/* A device, on the same terms as the hero's. It used to be a chrome-less
   cropped fragment on the theory that a fourth phone would turn the page into a
   device carousel — but the fragment read as a card, not as the product, and
   these are pushed screens (they carry a back arrow) that belong inside
   something. Same bezel recipe, same --pw-driven type scale, same tab bar. */
.showcase-phone { display: grid; place-items: center; width: 100%; }
.ui-frame {
  /* Narrow enough that a 19.5:9 device does not tower over the tab column
     beside it; wide enough that the UI keeps the 14px it was drawn at.
     Everything else about the shell — rail, radius, buttons — comes from the
     shared iPhone recipe further down, which is driven off this --pw. */
  --pw: clamp(240px, 22vw, 280px);
}
.ui-screen {
  display: flex;
  flex-direction: column;
  /* The glass is exactly the status bar plus one 750x1624 capture at full
     width — 0.135 of the device width over 0.944 of it, hence the second term.
     Pinning it here rather than letting the image decide is what keeps the one
     drawn panel the same height as the three real ones, so switching tabs
     never moves the device. It runs a touch taller than a real 19.5:9 iPhone
     because the captures were taken without an iOS status bar and this draws
     one above them; recapture with the status bar and this becomes 9 / 19.5. */
  aspect-ratio: 1 / calc(1624 / 750 + 0.135 / 0.944);
  background:
    radial-gradient(280px 220px at 88% 0%, rgb(var(--accent-rgb) / 0.07), transparent 70%),
    var(--app-bg);
  font-size: max(11px, calc(var(--pw) / 20));
  line-height: 1.45;
  color: var(--app-text);
}
/* Room for the tab bar the drawn panel carries, which sits over its body. */
.ui-screen .sc-panel .ph-body { padding-bottom: 4.2em; }
.ui-screen .ph-tabs { z-index: 2; }

/* A real capture fills the glass under the status bar. It carries the app's own
   header and tab bar inside the image, so nothing is drawn over it — the point
   of these panels is that they are the product, untouched. object-fit absorbs
   the sub-pixel difference between the panel's height and the image's. */
.sc-panel.is-shot { padding: 0; }
.sc-shot {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
}
/* The active tab follows the selected screen; landing.js sets data-tab. */
.ui-screen[data-tab="pot"] .ph-tab[data-tab="pot"],
.ui-screen[data-tab="more"] .ph-tab[data-tab="more"] {
  color: var(--app-primary);
  font-weight: 600;
  background: rgb(var(--accent-rgb) / 0.1);
  border: 1px solid rgb(var(--accent-rgb) / 0.2);
}
.sc-panel { display: none; opacity: 0; }
.sc-panel.on {
  display: flex;
  flex-direction: column;
  flex: 1;
  animation: scFadeIn 0.22s var(--ease) forwards;
}
/* The screen is sized by its tallest panel, so shorter ones had slack under the
   last card. Where a panel ends in the primary action, that action goes where
   the app puts it — against the bottom of the screen — and the slack becomes
   the gap a real phone leaves above the thumb. */
.sc-panel .ph-body {
  display: flex;
  flex-direction: column;
  gap: 0.6em;
}
.sc-panel .ph-body > .ph-cta { margin-top: auto; }

/* The panel enters from the direction you travelled, so moving down the tab
   list reads as moving through a list rather than as four unrelated swaps.
   --sc-dir is +1 going down, -1 going up; landing.js sets it on the showcase. */
@keyframes scFadeIn {
  from { opacity: 0; transform: translateY(calc(var(--sc-dir, 1) * 9px)); }
  to { opacity: 1; transform: translateY(0); }
}

/* Switching a tab should read as a screen loading, not as a slide swapping.
   The panel's own blocks arrive just behind it, ~55ms apart and capped at four
   steps, which is the difference between "a new view" and "a new image". */
@keyframes sc-row-in {
  from { opacity: 0; transform: translateY(7px); }
}
@media (prefers-reduced-motion: no-preference) {
  .sc-panel.on .ph-body > * {
    animation: sc-row-in 0.34s var(--ease) backwards;
  }
  .sc-panel.on .ph-body > :nth-child(1) { animation-delay: 0.06s; }
  .sc-panel.on .ph-body > :nth-child(2) { animation-delay: 0.115s; }
  .sc-panel.on .ph-body > :nth-child(3) { animation-delay: 0.17s; }
  .sc-panel.on .ph-body > :nth-child(n + 4) { animation-delay: 0.225s; }
}

/* The tab list advances on its own every 6s. Without a tell, the screen simply
   changes under you and reads as a glitch; this is the countdown made visible.
   landing.js owns .is-auto and drops it the moment you take over — hover,
   focus, tap, or the section leaving the viewport. */
.sc-tab[aria-selected="true"]::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 2px;
  transform: scaleX(0);
  transform-origin: left center;
  background: linear-gradient(90deg, var(--accent-deep), var(--accent));
  opacity: 0.55;
  pointer-events: none;
}
/* Its own keyframe, not `bar-fill`: that one only declares `from`, and the
   resting transform here is already scaleX(0), so it would have run in place. */
@keyframes sc-countdown {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}
@media (prefers-reduced-motion: no-preference) {
  .showcase.is-auto .sc-tab[aria-selected="true"]::after {
    animation: sc-countdown 6s linear forwards;
  }
}

/* Secondary features: the rest of the app, at a glance. A ruled index, not six
   more cards — the tabs above are already the card-shaped thing in this
   section, and nothing here is clickable, so card chrome and a lift-on-hover
   were promising an affordance that does not exist. Rules give the same
   grouping at a third of the visual weight, and the cells hug their content
   instead of padding out to a shared 168px. */
/* Secondary feature cards header (.fgrid-head) */
.fgrid-head {
  margin-top: clamp(56px, 7vw, 84px);
  margin-bottom: 22px;
}
.fgrid-eyebrow {
  display: flex;
  align-items: center;
  gap: 16px;
  color: var(--accent);
  font-family: var(--display);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  margin-bottom: 10px;
}
/* The label stays — it says which part of the app these belong to — but the
   fading rule beside it does not. A tracked eyebrow trailing an ornament is the
   one section grammar this page had deliberately refused everywhere else. */
.fgrid-title {
  font-family: var(--display);
  font-size: clamp(22px, 2.8vw, 30px);
  font-weight: 700;
  color: var(--text);
  margin: 0;
  letter-spacing: -0.02em;
}

/* Secondary features. Six things, none of them clickable, so they get the same
   grammar as the problem columns: a rule across the container, an accent tick
   where each cell meets it, hairlines between cells, and nothing else. They
   used to be blurred glass cards that lifted and glowed on hover — an
   affordance promising a destination none of them has, and the same blur the
   final CTA was using to look like a different site. */
.fgrid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0;
  margin-top: clamp(20px, 2.4vw, 30px);
  border-top: 1px solid var(--line);
}
.fgrid-item {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 9px;
  padding: 24px 34px 26px 0;
}
.fgrid-item:not(:nth-child(3n + 1)) { padding-left: 34px; }
/* The divider lives on the gap, so the first column stays flush with the
   headline's own left edge and the band lines up with the section spine. */
.fgrid-item:not(:nth-child(3n + 1))::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 1px;
  background: var(--line);
}
/* The second row carries its own rule, and every tick sits on whichever rule
   its cell actually meets. */
.fgrid-item:nth-child(n + 4) { border-top: 1px solid var(--line); }
.fgrid-item::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  width: 26px;
  height: 1px;
  background: var(--accent);
}
.fgrid-item:not(:nth-child(3n + 1))::before { left: 34px; }
/* The glyph without its plate. The plate was card chrome on a cell that is not
   a card; the icon still carries which part of a season this is. */
.fgrid-ic {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  color: var(--accent);
  flex-shrink: 0;
}
.fgrid-ic svg {
  width: 22px;
  height: 22px;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.fgrid-item b {
  font-family: var(--display);
  font-size: 16.5px;
  font-weight: 700;
  color: var(--text);
  letter-spacing: -0.01em;
  margin-top: 2px;
}
.fgrid-item > span:last-child {
  color: var(--muted);
  font-size: 13.5px;
  line-height: 1.45;
}
.fgrid-dots {
  display: none;
}


/* ------------------------------------------------------------- iPhone --- */
/* An actual iPhone, not a rounded rectangle: 19.5:9 glass, the titanium rail
   with its four button cutouts, the Dynamic Island and the home indicator.
   Every measurement is a ratio of --pw (device width), taken off a 393pt
   iPhone, so one recipe holds at any size and never drifts out of proportion:
     corner radius 0.175 · rail 0.028 · island 0.315 x 0.093 · status bar 0.135
   Set --pw on .iphone and everything follows. */
.iphone { --pw: 248px; }
.iphone,
.ui-frame,
.phone {
  --rail: calc(var(--pw) * 0.028);
  position: relative;
  width: var(--pw);
  padding: var(--rail);
  border-radius: calc(var(--pw) * 0.175);
  /* Brushed titanium: light catches the two corners the page lights from. */
  background:
    linear-gradient(122deg,
      #8f96a2 0%, #4a5058 6%, #22262c 17%, #171a1f 34%,
      #464c55 50%, #14171b 66%, #21252b 83%, #565c66 94%, #8a9099 100%);
  box-shadow:
    inset 0 0 0 1px rgb(var(--surface-rgb) / 0.14),
    inset 0 1px 1px rgb(var(--surface-rgb) / 0.22),
    0 32px 64px -34px rgba(0, 0, 0, 0.95),
    0 4px 12px -4px rgba(0, 0, 0, 0.6);
}
/* Buttons, drawn as two background layers on ONE pseudo-element rather than as
   eight elements of markup: the left column carries the action button and the
   volume pair, the right column the side button. Both live on ::before so
   ::after stays free — the hero's side phones already use theirs for the
   depth wash, and two rules on one pseudo-element merge into nonsense. */
.iphone::before,
.ui-frame::before,
.phone::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(var(--pw) * -0.006);
  right: calc(var(--pw) * -0.006);
  pointer-events: none;
  background-repeat: no-repeat;
  background-size: calc(var(--pw) * 0.011) 100%;
  background-position: left top, right top;
  background-image:
    linear-gradient(180deg,
      transparent 0 15.5%, #7e8590 15.5% 19.5%,
      transparent 19.5% 24%, #7e8590 24% 32%,
      transparent 32% 34%, #7e8590 34% 42%,
      transparent 42% 100%),
    linear-gradient(180deg,
      transparent 0 27%, #7e8590 27% 40%, transparent 40% 100%);
}

.iphone-screen,
.ui-screen,
.phone-screen {
  position: relative;
  overflow: hidden;
  border-radius: calc(var(--pw) * 0.148);
}
/* --sb is the capture's own top colour, so the status bar reads as part of the
   app rather than as a strip glued above it. */
.iphone-screen { background: var(--sb, var(--app-bg)); }
/* Home indicator, over the app's tab bar exactly as it sits on the device. */
.iphone-screen::after,
.ui-screen::after,
.phone-screen::after {
  content: "";
  position: absolute;
  z-index: 3;
  left: 50%;
  bottom: calc(var(--pw) * 0.026);
  transform: translateX(-50%);
  width: calc(var(--pw) * 0.355);
  height: calc(var(--pw) * 0.0135);
  border-radius: 999px;
  background: rgb(var(--surface-rgb) / 0.62);
}

.iphone-status {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: calc(var(--pw) * 0.135);
  padding: 0 calc(var(--pw) * 0.072) calc(var(--pw) * 0.012);
  color: #fff;
}
.iphone-time {
  font-family: var(--display);
  font-size: calc(var(--pw) * 0.048);
  font-weight: 600;
  letter-spacing: 0.01em;
  line-height: 1;
}
.iphone-status-ic {
  width: calc(var(--pw) * 0.145);
  height: auto;
  opacity: 0.95;
}
.iphone-island {
  position: absolute;
  z-index: 4;
  top: calc(var(--pw) * 0.029);
  left: 50%;
  transform: translateX(-50%);
  width: calc(var(--pw) * 0.315);
  height: calc(var(--pw) * 0.093);
  border-radius: 999px;
  background: #000;
}
/* The front camera, off-centre inside the island the way it actually is. */
.iphone-island::after {
  content: "";
  position: absolute;
  top: 50%;
  right: 13%;
  transform: translateY(-50%);
  width: 21%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(circle at 34% 30%, #29344c 0%, #0b0f17 62%, #04060a 100%);
}
.iphone-shot {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 750 / 1624;
  object-fit: cover;
  object-position: top center;
}

/* ---------------------------------------------------------------- trust --- */

/* ------------------------------------------------------------------ faq --- */
/* Full measure, like every other section body. The question and the marker take
   the two ends of the row and the whole row is the target; the answer keeps a
   readable 68ch underneath. */
.faq-list { max-width: none; }
.faq-item {
  border-bottom: 1px solid var(--line);
  transition: background 0.25s var(--ease);
}
/* Open answers read as attached to their question rather than as loose body
   copy sitting between two rules. */
.faq-item[open] {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.028), transparent 78%);
}
.faq-item summary {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 18px;
  padding: 21px 14px 21px 12px;
  cursor: pointer;
  list-style: none;
  font-family: var(--display);
  font-size: clamp(16.5px, 1.5vw, 18.5px);
  font-weight: 600;
  text-wrap: balance;
  transition: color 0.2s var(--ease);
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary:hover { color: var(--accent-soft); }
.faq-item[open] summary { color: var(--accent); }
.faq-icon {
  position: relative;
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  margin-top: 3px;
}
.faq-icon::before,
.faq-icon::after {
  content: "";
  position: absolute;
  inset: 50% 0 auto 0;
  height: 1.5px;
  border-radius: 2px;
  background: currentColor;
  transition: transform 0.3s var(--ease);
}
.faq-icon::after { transform: rotate(90deg); }
.faq-item[open] .faq-icon::after { transform: rotate(0deg); }
.faq-answer {
  padding: 0 14px 24px 12px;
  max-width: 68ch;
  color: var(--muted);
  font-size: 15.5px;
  text-wrap: pretty;
}

/* ---------------------------------------------------------------- final --- */
/* The ending is the half of the page people remember, and it was the least
   designed thing on it: a 1320px single line of display type reading as a
   banner strip, two actions at the same weight, and a brand glow so faint it
   was texture rather than light. Same words, given somewhere to land. */
.final {
  position: relative;
  padding-block: clamp(96px, 13vw, 168px);
  border-top: 1px solid var(--line);
  background:
    radial-gradient(52% 78% at 50% 34%, rgb(var(--accent-rgb) / 0.16), transparent 70%),
    radial-gradient(80% 100% at 50% 0%, rgb(var(--accent-rgb) / 0.07), transparent 72%),
    var(--bg-2);
  overflow: hidden;
}
.final::before {
  content: "";
  position: absolute;
  inset: 0;
  background: url("../brand/pattern-grid.svg") center 30% / 340px auto repeat;
  opacity: 0.34;
  -webkit-mask-image: radial-gradient(58% 68% at 50% 42%, #000 5%, transparent 72%);
  mask-image: radial-gradient(58% 68% at 50% 42%, #000 5%, transparent 72%);
  pointer-events: none;
}
.final .wrap { position: relative; }
.final h2 {
  /* A measure, so the question breaks into a statement instead of a strip. */
  max-width: 14ch;
  font-size: clamp(32px, 5vw, 62px);
  font-weight: 800;
  line-height: 1.04;
  letter-spacing: -0.015em;
  text-wrap: balance;
}
.final p {
  margin: 22px 0 0;
  max-width: 46ch;
  color: var(--muted);
  font-size: clamp(15.5px, 1.3vw, 17.5px);
  text-wrap: pretty;
}
/* The two actions stop being twins: one is the thing to do, the other is a
   way back into the page. */
.final-cta { display: flex; flex-wrap: wrap; justify-content: flex-start; align-items: center; gap: 14px; margin-top: 0; }
.final-cta .btn-primary { min-height: 54px; padding-inline: 30px; font-size: 16px; box-shadow: 0 8px 24px -6px rgb(var(--accent-rgb) / 0.4); }
.final-cta .btn-ghost { background: none; border-color: var(--line); color: var(--muted); }
.final-cta .btn-ghost:hover { color: var(--text); border-color: var(--line-strong); }
.final-note {
  display: grid;
  gap: 6px;
  margin: 0;
  max-width: 34ch;
  color: var(--faint);
  font-size: 13.5px;
  line-height: 1.5;
}
.final-note b { color: var(--muted); font-weight: 600; }

/* The ending used to be a glass card carrying a tinted pill with a glowing dot
   — the same widget the hero comment above calls the most recognisable tell of
   a generated landing page, taken off the top of the page and reinstalled at
   the bottom of it. Peak-end makes this half of what anyone remembers, so it is
   built from the page's own spine instead: a rule across the container with an
   accent tick where it starts, the question on the left, the action and the
   promise on the right. No blur, no card, nothing borrowed. */
.final-inner {
  position: relative;
  display: grid;
  grid-template-columns: 7fr 4fr;
  gap: clamp(28px, 5vw, 72px);
  align-items: start;
  padding-top: clamp(30px, 3.6vw, 48px);
  border-top: 1px solid var(--line);
}
/* The same marker the problem columns use where they meet their rule. */
.final-inner::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  width: 26px;
  height: 1px;
  background: var(--accent);
}
.final-act {
  display: flex;
  flex-direction: column;
  gap: 18px;
}
@media (max-width: 900px) {
  .final-inner {
    grid-template-columns: 1fr;
    gap: 26px;
  }
}
.final-sub-text {
  margin: 18px 0 0;
  max-width: 46ch;
  color: var(--muted);
  font-size: clamp(15px, 1.3vw, 17px);
  line-height: 1.5;
}

/* --------------------------------------------------------------- footer --- */
.footer { border-top: 1px solid var(--line); padding-block: 56px 32px; }
/* Flex, not a fixed grid: columns whose pages do not exist yet are removed at
   runtime, and the footer has to stay balanced with however many remain. */
.footer-top {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(26px, 3vw, 48px);
}
/* Two groups, not four floating columns: identity on the left, every link
   column clustered on the right at a consistent gap. The brand used to grow to
   absorb the slack, which spread the surviving columns into the middle with a
   hole on either side of them. */
.footer-brand { flex: 0 1 360px; margin-right: auto; }
.footer-col { flex: 0 0 180px; }
.footer-brand p {
  margin-top: 14px;
  max-width: 34ch;
  color: var(--faint);
  font-size: 14px;
  text-wrap: pretty;
}
/* h3, not h5: these used to jump two levels down from the last section
   heading, which reads to a screen reader as two missing levels of structure.
   The styling is unchanged — only the level is now honest. */
.footer-col .footer-h {
  font-size: 12px;
  font-weight: 600;
  color: var(--faint);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin-bottom: 14px;
}
.footer-col a {
  display: block;
  padding: 7px 0;
  color: var(--muted);
  font-size: 14.5px;
  transition: color 0.2s;
}
.footer-col a:hover { color: var(--text); }
.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin-top: 46px;
  padding-top: 24px;
  border-top: 1px solid var(--line);
  color: var(--faint);
  font-size: 13px;
}

/* ----------------------------------------------------------- mobile CTA --- */
.mcta-bar {
  position: fixed;
  z-index: var(--z-sticky);
  inset: auto 0 0 0;
  display: none;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 16px calc(10px + env(safe-area-inset-bottom, 0px));
  background: rgba(11, 11, 16, 0.95);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-top: 1px solid var(--line-strong);
  transform: translateY(110%);
  transition: transform 0.35s var(--ease);
}
.mcta-bar.show { transform: none; }
.mcta-note { color: var(--muted); font-size: 13px; font-weight: 500; }
.mcta-bar .btn { flex-shrink: 0; }

/* --------------------------------------------------------------- motion --- */
/* Visible is the default state. Only a JS-enabled page that has NOT yet
   revealed an element hides it, so no-JS and failed-observer both render
   the full page — there is no rule pair whose order could strand content. */
.reveal { opacity: 1; transform: none; }
html.js .reveal { transition: opacity 0.6s var(--ease), transform 0.6s var(--ease); }
html.js .reveal:not(.in) { opacity: 0; transform: translateY(16px); }
html.js .reveal.d1 { transition-delay: 0.08s; }
html.js .reveal.d2 { transition-delay: 0.16s; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.12s !important;
  }
  html.js .reveal,
  html.js .reveal:not(.in) { opacity: 1; transform: none; transition: none; }
  /* Phones are simply already in place: no travel, no fade, no parallax.
     The entrance keyframes are inside a no-preference query, so they never
     run here; this only cancels the transition used by the parallax. */
  .phone-cluster .phone { transition: none; }
}

/* ------------------------------------------------------------ responsive -- */
@media (max-width: 1100px) {
  .hero-inner {
    display: flex;
    flex-direction: column;
    gap: 28px;
  }
  .hero-stage { order: 1; max-width: 640px; margin-inline: auto; }
  .hero-copy { order: 2; max-width: 640px; }
  .hero-apps { order: 3; }
  .phone-cluster { --pw: clamp(200px, 30vw, 250px); }
  .callout-pay { top: 4px; right: -6px; }
  .callout-vote { bottom: 12px; left: -6px; right: auto; }
  /* The row gap now carries the separation, so the verdict drops its margin.
     The rule still spans the section; only the measure pulls in. */
  .prob-close { margin-top: 0; padding-right: 30%; }
  .showcase { grid-template-columns: 1fr; }
  .showcase-phone { order: -1; }
}

/* On a phone the four descriptive cards stacked to 524px — taller than the
   viewport minus the screen they control, so tapping a tab changed something
   already scrolled out of sight. Below 700px the tabs become a chip grid that
   sits above the panel: all four visible at once, the selected one spanning
   the row to keep its description. Cause above effect, in one screen. */
@media (max-width: 700px) {
  /* The device leads here too. The list below it is compact — only the selected
     tab carries its description — so the screen it controls stays on the same
     viewport, and `initTabs` scrolls it back into frame on a tap in case it does
     not. */
  .showcase-phone { order: -1; }
  /* One column here, so the device gets the same width the hero's does rather
     than sitting at the two-column minimum. */
  .ui-frame { --pw: min(calc(100vw - var(--gutter) * 2), 320px); }

  /* A vertical list, not a chip grid.
     Four chips on two columns with the selected one spanning the full width
     can never be a rectangle: the remaining three left a hole, and because the
     spanning chip is whichever one you last tapped, the hole moved around the
     grid as you used it. The two chips that shared a row also disagreed on
     height whenever a title wrapped. A list has none of those problems, is the
     same shape on every tap, and is what the wide layout already does. */
  .showcase-nav {
    display: flex;
    flex-direction: column;
    gap: 8px;
  }
  .sc-tab {
    display: grid;
    grid-template-columns: 34px minmax(0, 1fr);
    align-items: center;
    gap: 12px;
    min-height: 56px;
    padding: 12px 14px;
    border-radius: 14px;
    border: 1px solid var(--line);
    background: rgba(255, 255, 255, 0.015);
    text-align: left;
    transition: background 0.2s var(--ease), border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
  }
  .sc-tab-ic {
    width: 32px;
    height: 32px;
    border-radius: 9px;
    transition: width 0.2s var(--ease), height 0.2s var(--ease);
  }
  .sc-tab-ic svg {
    width: 16px;
    height: 16px;
  }
  .sc-tab h3 { font-size: 14.5px; line-height: 1.25; }
  .sc-tab p { display: none; }
  .sc-tab[aria-selected="true"] {
    align-items: start;
    gap: 11px;
    border-color: rgb(var(--accent-rgb) / 0.35);
    background: rgba(255, 122, 26, 0.06);
    box-shadow: 0 8px 24px -10px rgba(255, 122, 26, 0.25), inset 3px 0 0 var(--accent);
  }
  .sc-tab[aria-selected="true"] .sc-tab-ic {
    width: 34px;
    height: 34px;
  }
  .sc-tab[aria-selected="true"] h3 { font-size: 16px; color: var(--text); }
  .sc-tab[aria-selected="true"] p { display: block; margin-top: 4px; color: var(--muted); }
}

@media (max-width: 920px) {
  :root { --nav-h: 68px; }
  /* The centre track has nothing to hold once the menu becomes a fixed sheet,
     so the bar goes back to a plain two-end row. */
  .nav-inner {
    width: 100%;
    display: flex;
    justify-content: space-between;
    gap: 14px;
  }
  /* Thumb targets: the mark and the wordmark are only 30px of ink, and the
     language pills 34px. Padding grows the hit box without moving the ink. */
  .brand {
    margin-right: auto;
    padding-block: 7px;
  }
  .lang-btn { min-height: 38px; }
  .nav-toggle { display: flex; }
  .nav-links {
    position: fixed;
    inset: 0 0 auto 0;
    width: 100%;
    box-sizing: border-box;
    flex-direction: column;
    align-items: stretch;
    gap: 4px;
    margin: 0;
    padding: calc(var(--nav-h) + 20px) 20px calc(24px + env(safe-area-inset-bottom, 0px));
    background: rgba(7, 7, 10, 0.98);
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
    border-bottom: 1px solid var(--line);
    transform: translateY(-100%);
    visibility: hidden;
    transition: transform 0.35s var(--ease), visibility 0.35s;
    max-height: 100dvh;
    overflow-y: auto;
  }
  .nav-links.open { transform: none; visibility: visible; }
  /* brand and controls stay above the sheet so the close button is reachable */
  .nav-inner > .brand, .nav-actions { position: relative; z-index: 2; }
  .nav-mobile-login, .nav-mobile-cta { display: flex; }
  .nav-links > a {
    padding: 14px 12px;
    font-size: 17px;
    font-weight: 600;
    color: var(--text);
    border-radius: 12px;
  }
  .nav-links > a[aria-current="true"]::after { display: none; }
  .nav-links > a[aria-current="true"] { background: var(--accent-tint); color: var(--accent); }
  .nav-divider { display: block; height: 1px; margin: 12px 0; border: 0; background: var(--line); }
  .nav-mobile-login {
    justify-content: center;
    padding: 14px 12px;
    border-radius: 12px;
    color: var(--muted);
    font-family: var(--display);
    font-size: 16px;
    font-weight: 600;
    text-align: center;
  }
  .nav-mobile-cta { width: 100%; }
  .nav-desktop-only { display: none; }

  /* One column: there is nothing left to align across, so the shared-row grids
     hand back to ordinary stacking and get their gaps returned. */
  .steps {
    grid-template-columns: 1fr;
    gap: 16px;
  }
  .step { padding: 22px 18px; border-radius: 18px; }



  .fgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }

  .mcta-bar { display: flex; }
  body { padding-bottom: 0; }
}

/* ------------------------------------------- scroll choreography (mobile) --
   CSS scroll-driven animations: no JS, no scroll listeners, and the compositor
   does the work. Every one is guarded twice — @supports for engines without a
   view timeline (Firefox keeps the one-shot behaviour it has today) and a
   reduced-motion query — and every one moves transform or opacity only.
   -------------------------------------------------------------------------- */
@keyframes app-scroll {
  to { transform: translateY(calc(var(--pw) * -0.13)); }
}
@keyframes flow-travel {
  from { transform: translateY(-14px); }
  to { transform: translateY(14px); }
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {

    /* The screen scrolls with the page, the way the app scrolls under your
       thumb. The header is promoted and made opaque so the content passes
       beneath it instead of over it — which is what a real app does, and what
       stops a transform from painting on top of its own chrome. */
    @media (max-width: 760px) {
      .phone-main .ph-head {
        position: relative;
        z-index: 2;
        background: var(--app-bg);
      }
      /* The screen itself is overflow:hidden, so an anonymous view() inside it
         would resolve against the screen. The device publishes a named
         timeline from outside that clip instead. */
      .phone-main { view-timeline-name: --hero-screen; }
      .phone-main .ph-body {
        animation: app-scroll linear both;
        animation-timeline: --hero-screen;
        animation-range: entry 100% exit 0%;
      }
    }

    /* Mobile only: the wide layout keeps the authored one-shot it already has.
       The brief was the phone, so the desktop beat is left exactly as it was. */
    @media (max-width: 760px) {


    }
  }
}

/* ---------------------------------------------- mobile art direction ------
   On a phone every section is the same full-width block on the same black, so
   a long scroll reads as one uniform ribbon. Each section gets its own
   atmosphere instead: the problem runs cold and blue because it is the
   antagonist, the Hall sections run warm because they are the resolution, and
   the quiet ones stay neutral so the warm ones mean something. Bloom only —
   no borders, no bands — so the seams never show.
   -------------------------------------------------------------------------- */
@media (max-width: 760px) {
  main > section { background-color: transparent; }

  /* The handoff: neutral above, the accent arriving below. */
  #split {
    background-image:
      radial-gradient(120% 60% at 50% 108%, rgb(var(--accent-rgb) / 0.1), transparent 70%);
  }
  /* Cold. The only place on the page that leaves the brand hue. */
  #problem {
    background-image:
      radial-gradient(110% 50% at 15% 0%, rgba(96, 130, 190, 0.09), transparent 68%),
      radial-gradient(90% 40% at 90% 60%, rgba(120, 100, 160, 0.055), transparent 72%);
  }
  #how {
    background-image:
      radial-gradient(100% 45% at 85% 5%, rgb(var(--accent-rgb) / 0.055), transparent 70%);
  }
  /* The product itself: focused, almost lit from the screen. */
  #product {
    background-image:
      radial-gradient(90% 45% at 50% 40%, rgb(var(--accent-rgb) / 0.075), transparent 72%);
  }
  #features {
    background-image:
      radial-gradient(110% 40% at 10% 8%, rgb(var(--accent-rgb) / 0.07), transparent 68%);
  }

  #faq {
    background-image:
      radial-gradient(100% 40% at 50% 0%, rgb(var(--surface-rgb) / 0.03), transparent 70%);
  }
}

@media (max-width: 760px) {
  :root { --section-y: clamp(36px, 7vw, 56px); }
  .hero { padding-top: 78px; padding-bottom: 24px; }
}

/* The hero's phone, wherever the viewport is phone-shaped.
   844x390 is a landscape phone: too wide for the 760px breakpoint, far too
   short for a 541px cluster. Both conditions get the same single screen. */
@media (max-width: 760px), (orientation: landscape) and (max-height: 560px) {
  /* Sleek centered mobile iPhone device mockup at top */
  .phone-side { display: none; }

  .hero-stage {
    position: relative;
    width: 100%;
    max-width: 320px;
    margin: 0 auto;
    padding: 0;
  }

  .phone-cluster {
    --pw: clamp(250px, 76vw, 290px);
    display: flex;
    justify-content: center;
    align-items: center;
    position: relative;
    height: auto;
    perspective: none;
  }

  .phone-cluster .phone.phone-main {
    position: relative;
    /* The wide cluster pins this phone with `left: 50%` and pulls it back with a
       `translate3d(-50% ...)` that also carries the pointer parallax. Going
       relative here kept both, so the screen sat ~35px right of centre and the
       parallax still nudged it. The flex parent does the centring now. */
    left: auto;
    transform: none;
  }

  /* The device itself — bezel, radius, Dynamic Island, buttons — is the shared
     iPhone shell at every width. This used to be a second hand-cut mockup that
     only existed below 760px, with its own notch drawn on ::before, which is
     where the shell puts the volume buttons. */
  .phone-main .phone-screen {
    /* Shorter glass than a real device, so the hero copy above it still has a
       screen to overlap on a phone. */
    aspect-ratio: 9 / 17.5;
    font-size: clamp(11.5px, calc(var(--pw) / 20.5), 14px);
  }

  /* The screen dissolves into the headline rather than ending at it. The fade
     starts at roughly the top third of the phone — 320px up from its foot on a
     390px viewport — so by the time the copy climbs over it there is nothing
     left to cover, and the two read as one object instead of a stack. */
  .hero-stage::after {
    content: "";
    position: absolute;
    inset: auto 0 0 0;
    height: 320px;
    background: linear-gradient(
      180deg,
      transparent 0%,
      rgba(7, 7, 10, 0.18) 14%,
      rgba(7, 7, 10, 0.52) 34%,
      rgba(7, 7, 10, 0.82) 58%,
      rgba(7, 7, 10, 0.96) 80%,
      var(--bg) 100%
    );
    pointer-events: none;
    z-index: 6;
  }

  .phone-main .ph-act.is-extra { display: flex; }

  /* Climbs into the faded half of the screen above it. -32px only cleared the
     phone's own foot; the copy now sits well inside the gradient, which is what
     makes the fade look deliberate rather than like a cropped screenshot. */
  .hero-copy {
    text-align: center;
    margin-top: -110px;
    position: relative;
    z-index: 7;
  }
  .hero h1 {
    font-size: clamp(27px, 7.2vw, 36px);
    margin-top: 0;
    max-width: none;
  }
  .hero-sub {
    font-size: 15px;
    margin-top: 10px;
    margin-inline: auto;
    max-width: 42ch;
  }
  .hero-cta {
    display: grid;
    grid-template-columns: 1fr;
    gap: 10px;
    margin-top: 20px;
  }
  .hero-cta .btn { width: 100%; }
  .hero-cta .btn-ghost {
    background: none;
    border-color: var(--line);
    color: var(--muted);
  }

  /* Store marks sit harmoniously below CTA on mobile */
  .hero-apps {
    position: relative;
    z-index: 7;
    margin-top: 18px;
    text-align: center;
  }
  .hero-apps-note {
    font-size: 11px;
    color: var(--faint);
  }
  .hero-apps-badges {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 12px;
    margin-top: 8px;
  }
  .badge-apple { height: 36px; }
}

@media (prefers-reduced-motion: no-preference) {
  @media (max-width: 760px), (orientation: landscape) and (max-height: 560px) {
    .hero-stage.animate .phone-main { animation: phone-in-solo 0.7s var(--ease) backwards; }
  }
}

/* Landscape phone. Vertically there is no room for a stacked hero — 88px of
   top padding alone is a quarter of a 390px viewport — so the copy and the
   screen go back to sitting side by side, which is what the width affords. */
@media (orientation: landscape) and (max-height: 560px) {
  .hero {
    padding-top: 88px;
    padding-bottom: 36px;
  }
  .hero-stage { max-width: none; padding: 0; }
  .hero-inner {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
    gap: clamp(24px, 4vw, 44px);
    align-items: center;
  }
  .hero-copy { max-width: none; }
  .hero h1 { margin-top: 14px; font-size: clamp(26px, 4.2vw, 36px); }
  .hero-sub { margin-top: 12px; font-size: 15.5px; }
  .hero-cta {
    display: flex;
    flex-direction: row;
    margin-top: 20px;
  }
  .hero-cta .btn { width: auto; }
  /* Wider than the portrait cap would allow relative to the column, because the
     copy sits beside it now and the screen is the half doing the selling. */
  .phone-cluster { --pw: min(42vw, 300px); }
  /* A 60px bar is a sixth of a 390px-tall viewport; it earns less of it here. */
  .mcta-bar { padding-block: 7px; }
}

/* Everything below is the phone layout. The opener went missing at some point
   and ~200 lines of it were applying at every width — the handoff stacked on
   desktop, the feature grid became a scroller, the footer broke its row. */
@media (max-width: 760px) {
  /* Secondary features intro header on mobile */
  .fgrid-head {
    margin-top: 32px;
    margin-bottom: 12px;
  }
  .fgrid-title {
    font-size: 18px;
    margin-top: 6px;
  }

  /* Compact Horizontal Snap Track on Mobile: takes ONLY 140px vertical space! */
  /* A horizontal snap track, same grammar: the rule runs across the top and
     each cell is separated by a hairline rather than boxed in chrome. */
  .fgrid {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    gap: 0;
    margin-top: 14px;
    padding-bottom: 12px;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .fgrid::-webkit-scrollbar { display: none; }

  .fgrid-item {
    flex: 0 0 clamp(190px, 58vw, 230px);
    scroll-snap-align: start;
    gap: 8px;
    padding: 18px 18px 20px 0;
    border-top: 0;
    border-radius: 0;
    background: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    box-shadow: none;
  }
  /* Flat track: the column rules are per-neighbour, not per-third. */
  .fgrid-item:not(:nth-child(3n + 1)) { padding-left: 0; }
  .fgrid-item + .fgrid-item { padding-left: 18px; }
  .fgrid-item:not(:nth-child(3n + 1))::after { content: none; }
  .fgrid-item + .fgrid-item::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 12px;
    left: 0;
    width: 1px;
    background: var(--line);
  }
  .fgrid-item::before { left: 0; }
  .fgrid-item + .fgrid-item::before { left: 18px; }
  .fgrid-ic {
    width: 20px;
    height: 20px;
    border-radius: 0;
    background: none;
    border: 0;
    color: var(--accent);
  }
  .fgrid-ic svg { width: 20px; height: 20px; }
  .fgrid-item b {
    font-size: 14.5px;
    font-weight: 700;
    color: var(--text);
    margin-top: 2px;
  }
  .fgrid-item > span:last-child {
    font-size: 12.5px;
    color: var(--muted);
    line-height: 1.35;
  }
  /* 18px of gap around a 6px dot puts the centres 24px apart, which is what
     lets each hit area below be a full 24px without running into its
     neighbour's. At the old 6px gap the pitch was 12px and the areas
     overlapped two deep: the dot you pressed was not the dot that won. */
  .fgrid-dots {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 18px;
    margin-top: 14px;
    margin-bottom: 4px;
  }
  .fgrid-dot {
    position: relative;
    width: 6px;
    height: 6px;
    border-radius: 999px;
    border: none;
    padding: 0;
    background: rgba(255, 255, 255, 0.2);
    cursor: pointer;
    transition: width 0.3s var(--ease), background-color 0.3s var(--ease);
  }
  /* The dot is 6px because that is how big a position indicator should look.
     The thing a finger has to hit is 24px, WCAG 2.5.8's floor, and it is
     invisible. The gap above is what keeps these from overlapping. */
  .fgrid-dot::before {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 24px;
    height: 24px;
    transform: translate(-50%, -50%);
  }
  .fgrid-dot.active {
    width: 22px;
    background: var(--accent);
  }
  .fgrid-dot:focus-visible {
    outline: 2px solid rgb(var(--accent-rgb) / 0.9);
    outline-offset: 5px;
  }


  /* Handoff Stage Mobile Adaptation — ultra-polished, clean, compact */
  .handoff-stage {
    grid-template-columns: 1fr;
    gap: 12px;
    margin-top: 24px;
  }
  .handoff-card {
    padding: 18px 16px;
    border-radius: 20px;
  }
  .handoff-card.handoff-app {
    background: rgba(14, 14, 20, 0.55);
    border: 1px solid rgba(255, 255, 255, 0.08);
  }
  .handoff-card.handoff-hall {
    background: rgba(18, 18, 26, 0.85);
    border: 1px solid rgb(var(--accent-rgb) / 0.35);
    box-shadow: 0 16px 36px -12px rgba(0, 0, 0, 0.8), 0 0 24px -6px rgb(var(--accent-rgb) / 0.15);
  }
  .handoff-card-head {
    gap: 4px;
    margin-bottom: 14px;
  }
  .handoff-badge {
    padding: 5px 12px;
    font-size: 12.5px;
    gap: 6px;
  }
  .handoff-subtitle {
    font-size: 12px;
    color: var(--muted);
  }
  .handoff-bridge {
    flex-direction: row;
    padding-block: 2px;
    gap: 10px;
  }
  .bridge-line {
    width: 100%;
    height: 1px;
    background: linear-gradient(90deg, rgba(255,255,255,0.06), rgb(var(--accent-rgb) / 0.45), rgba(255,255,255,0.06));
  }
  .bridge-pill {
    padding: 6px 12px;
    font-size: 10.5px;
    gap: 6px;
  }
  .handoff-pills.theirs {
    grid-template-columns: repeat(2, 1fr);
    gap: 8px;
  }
  .handoff-pills.theirs li {
    padding: 9px 10px;
    font-size: 12px;
    border-radius: 12px;
    gap: 8px;
    background: rgba(255, 255, 255, 0.035);
    border: 1px solid rgba(255, 255, 255, 0.07);
    color: var(--muted);
  }
  .handoff-groups {
    display: flex;
    flex-direction: column;
    gap: 12px;
  }
  .group-title {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--accent);
    opacity: 0.9;
    margin-bottom: 6px;
    display: block;
  }
  .handoff-pills.ours {
    grid-template-columns: repeat(3, 1fr);
    gap: 6px;
  }
  .handoff-pills.ours li {
    flex-direction: column;
    text-align: center;
    justify-content: center;
    padding: 10px 4px 8px;
    border-radius: 12px;
    gap: 6px;
    font-size: 11px;
    font-weight: 600;
    color: var(--text);
    background: rgba(255, 255, 255, 0.035);
    border: 1px solid rgb(var(--accent-rgb) / 0.18);
  }
  .handoff-pills.theirs .handoff-ic {
    width: 24px;
    height: 24px;
    border-radius: 7px;
    background: rgba(255, 255, 255, 0.06);
    color: var(--faint);
  }
  .handoff-pills.ours .handoff-ic {
    width: 28px;
    height: 28px;
    border-radius: 8px;
    background: rgb(var(--accent-rgb) / 0.12);
    border: 1px solid rgb(var(--accent-rgb) / 0.28);
    color: var(--accent);
  }
  .handoff-ic svg {
    width: 14px;
    height: 14px;
  }


  .split-label { font-size: 11px; gap: 12px; margin-bottom: 10px; }
  /* No room left to hold a measure back — the line wraps on its own. */
  .prob-close { padding-right: 0; }
  .footer-bottom { flex-direction: column; align-items: flex-start; gap: 10px; }
}

/* One, two, three. The columns rise and sharpen in the order the headline counts
   them, each dropping its divider and lighting its tick once it has landed — so
   the band assembles itself instead of fading in as a block. ~1.1s.

   Same gate as the handoff: without `html.js` the whole band, rules and ticks
   included, is simply there. */
html.js .prob-editorial.reveal { opacity: 1; transform: none; }

html.js .prob-editorial .prob-col {
  opacity: 0;
  transform: translateY(22px);
  filter: blur(6px);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease),
              filter 0.6s var(--ease);
}
html.js .prob-editorial.in .prob-col {
  opacity: 1;
  transform: none;
  /* blur(0px), not none: `none` is not a filter function, so browsers have no
     value to interpolate towards and the blur snaps off at the end. */
  filter: blur(0px);
}
html.js .prob-editorial.in .prob-col:nth-child(2) { transition-delay: 0.14s; }
html.js .prob-editorial.in .prob-col:nth-child(3) { transition-delay: 0.28s; }

/* The tick strikes out from the left edge after its column has settled. */
html.js .prob-editorial .prob-col::before {
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.42s var(--ease);
}
html.js .prob-editorial.in .prob-col::before { transform: scaleX(1); transition-delay: 0.34s; }
html.js .prob-editorial.in .prob-col:nth-child(2)::before { transition-delay: 0.48s; }
html.js .prob-editorial.in .prob-col:nth-child(3)::before { transition-delay: 0.62s; }

/* And the divider draws down behind it. */
html.js .prob-editorial .prob-col + .prob-col::after {
  transform: scaleY(0);
  transform-origin: top center;
  transition: transform 0.55s var(--ease);
}
html.js .prob-editorial.in .prob-col:nth-child(2)::after { transform: scaleY(1); transition-delay: 0.2s; }
html.js .prob-editorial.in .prob-col:nth-child(3)::after { transform: scaleY(1); transition-delay: 0.34s; }

/* Stacked, the same divider lies on its side, so it draws across instead of down. */
@media (max-width: 860px) {
  html.js .prob-editorial .prob-col + .prob-col::after {
    transform: scaleX(0);
    transform-origin: left center;
  }
  html.js .prob-editorial.in .prob-col:nth-child(2)::after,
  html.js .prob-editorial.in .prob-col:nth-child(3)::after { transform: scaleX(1); }
}

/* ------------------------------------------------- handoff, second pass --- */
/* The two cards are not siblings. One side keeps the game and the other takes
   the league, and that has to be felt before it is read: your fantasy platform
   recedes to a strip of labels, the Hall is the only lit object in the section.
   Weight does the arguing, so the copy does not have to repeat itself. */

/* The side that stays where it is. No icon plate and almost no card chrome — a
   label is not a control, and four of them matching nine in weight had the
   hierarchy backwards. */
.handoff-pills.theirs li {
  background: rgb(var(--surface-rgb) / 0.02);
  border-color: rgb(var(--surface-rgb) / 0.05);
  color: var(--faint);
}
.handoff-pills.theirs .handoff-ic {
  background: none;
  color: rgb(var(--surface-rgb) / 0.3);
}

/* The side that arrives. Orange stays on the marks: nine accent icons only
   register while the three group names are quiet, so the labels step back to
   faint and the rule between groups does the separating instead. */
.handoff-pills.ours li {
  background: rgb(var(--accent-rgb) / 0.045);
  border-color: rgb(var(--accent-rgb) / 0.14);
}
.group-title {
  color: var(--faint);
  opacity: 1;
}
.handoff-group + .handoff-group {
  padding-top: 14px;
  border-top: 1px solid rgb(var(--surface-rgb) / 0.06);
}

/* Lit along its top edge, with a shadow that falls somewhere. What was here
   was a zero-offset accent halo, which is decoration; this is the card sitting
   on the page. */
.handoff-card.handoff-hall {
  box-shadow:
    0 24px 60px -26px rgba(0, 0, 0, 0.9),
    inset 0 1px 0 0 rgb(var(--accent-rgb) / 0.14);
}

/* Movement belongs to pointers that can hover; touch already has a press. */
@media (hover: none) {
  .handoff-pills.ours li:hover {
    background: rgb(var(--accent-rgb) / 0.045);
    border-color: rgb(var(--accent-rgb) / 0.14);
    transform: none;
  }
}

/* The handoff, played once. Both cards settle, the rule between them draws out
   of the badge, and the Hall fills in a group at a time — the section performs
   the sentence its headline makes instead of fading in like everything else.
   ~1.1s end to end, and nothing loops.

   Gated on `html.js`, which the script only adds when it runs and reduced motion
   is off, so the default state is the finished one: a failed script cannot hide
   this section. The generic reveal is neutralised here for the same reason —
   two entrances on one element read as a stutter. */
html.js .handoff-stage.reveal {
  opacity: 1;
  transform: none;
}

html.js .handoff-stage .handoff-card {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.5s var(--ease), transform 0.5s var(--ease);
}
html.js .handoff-stage.in .handoff-card { opacity: 1; transform: none; }
html.js .handoff-stage.in .handoff-card.handoff-hall { transition-delay: 0.1s; }

/* Vertical in the gap between the two cards: it draws from the badge outward,
   so the badge reads as the thing joining them rather than a label pinned on a
   line that was already there. */
html.js .handoff-stage .bridge-line {
  transform: scaleY(0);
  transition: transform 0.5s var(--ease) 0.34s;
}
html.js .handoff-stage .bridge-line:first-child { transform-origin: bottom center; }
html.js .handoff-stage .bridge-line:last-child { transform-origin: top center; }
html.js .handoff-stage.in .bridge-line { transform: scaleY(1); }

html.js .handoff-stage .bridge-pill {
  opacity: 0;
  transform: scale(0.9);
  transition: opacity 0.3s var(--ease) 0.26s, transform 0.34s var(--ease) 0.26s;
}
html.js .handoff-stage.in .bridge-pill { opacity: 1; transform: none; }

/* Economy, governance, community — three beats, in the order the card reads. */
html.js .handoff-stage .handoff-group {
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.42s var(--ease), transform 0.42s var(--ease);
}
html.js .handoff-stage.in .handoff-group { opacity: 1; transform: none; }
html.js .handoff-stage.in .handoff-group:nth-child(1) { transition-delay: 0.48s; }
html.js .handoff-stage.in .handoff-group:nth-child(2) { transition-delay: 0.60s; }
html.js .handoff-stage.in .handoff-group:nth-child(3) { transition-delay: 0.72s; }

/* On a phone the same bridge lies on its side, so the rule draws across rather
   than down. Same timing, same order. */
@media (max-width: 760px) {
  html.js .handoff-stage .bridge-line { transform: scaleX(0); }
  html.js .handoff-stage .bridge-line:first-child { transform-origin: right center; }
  html.js .handoff-stage .bridge-line:last-child { transform-origin: left center; }
  html.js .handoff-stage.in .bridge-line { transform: scaleX(1); }
}

@media (max-width: 560px) {
  /* Deliberately still two columns. Collapsing to one turned this section into
     thirteen identical full-width bars — most of two screens saying nothing the
     pairing could not say in half the height. */
  .split-grid li { min-height: 58px; padding: 11px; gap: 8px; font-size: 13.5px; }
  .split-ic { width: 30px; height: 30px; flex-basis: 30px; }

  .final-cta { display: grid; grid-template-columns: 1fr; }
  .final-cta .btn { width: 100%; }
}

@media (max-width: 430px) {
  .showcase-nav { gap: 7px; }
  .sc-tab {
    min-height: 52px;
    padding: 11px 12px;
    column-gap: 10px;
  }
  .sc-tab-ic {
    width: 30px;
    height: 30px;
  }
  .sc-tab h3 { font-size: 13.5px; }
  .sc-tab[aria-selected="true"] { padding: 12px 12px 14px; }
  .sc-tab[aria-selected="true"] .sc-tab-ic {
    width: 32px;
    height: 32px;
  }
  .fgrid-item {
    grid-template-columns: 34px minmax(0, 1fr);
    padding: 13px 12px;
  }
  .fgrid-ic {
    width: 34px;
    height: 34px;
  }
  .fgrid-ic svg {
    width: 15.5px;
    height: 15.5px;
  }
}

@media (max-width: 380px) {
  :root { --gutter: 18px; }
  .brand-name { font-size: 17px; }
  .mcta-note { font-size: 12px; }
}

@media (min-width: 1600px) {
  :root { --maxw: 1280px; }
}

/* mobile devices pay a real cost for heavy compositing */
@media (max-width: 760px) {
  .hero::before { height: 460px; }
  .phone, .chaos, .product-demo-shell { box-shadow: 0 16px 32px -26px rgba(0, 0, 0, 0.9); }
  /* The trailer is 16:9 wherever it plays. Cropping it to a portrait box to
     fill the screen would cut the frame's own edges off, so the box keeps the
     video's shape and the cover copy stacks inside the shorter rectangle. */
  /* Full-bleed on a phone. The trailer is 16:9 with the device about a quarter
     of the frame wide, so width is legibility: dropping the gutter buys ~11%
     and an edge-to-edge frame reads as deliberate rather than cramped. The real
     fix is a 9:16 re-cut of the video, which is a video job, not a CSS one. */
  .product-demo-stage {
    margin-inline: calc(var(--gutter) * -1);
  }
  .product-demo-shell {
    aspect-ratio: 16 / 9;
    min-height: 0;
    max-height: 560px;
    border-radius: 0;
    border-left: 0;
    border-right: 0;
  }
  .product-demo-cover {
    gap: 14px;
    padding: 18px;
  }
  .product-demo-cover::before {
    background:
      radial-gradient(300px 190px at 50% 52%,
        rgba(7, 9, 13, 0.84) 0%,
        rgba(7, 9, 13, 0.52) 60%,
        transparent 84%),
      radial-gradient(340px 220px at 50% 0%, rgb(var(--accent-rgb) / 0.16), transparent 70%),
      linear-gradient(180deg, rgba(7, 9, 13, 0.24) 0%, rgba(7, 9, 13, 0.6) 100%);
  }
  /* A 16:9 box on a phone is ~200px tall. The logo tile and the note are the
     two pieces the card can lose and still say what it is — the wordmark is
     three sections up in the nav, and the note repeats what play implies. */
  .product-demo-logo { display: none; }
  .product-demo-note { display: none; }
  .product-demo-title { font-size: 1.35rem; }
  .product-demo-play { width: 52px; height: 52px; }
}

@media (prefers-reduced-motion: reduce) {
  .product-demo-frame,
  .product-demo-cover,
  .product-demo-loading,
  .product-demo-play {
    transition: none;
  }
  .product-demo-spinner {
    animation: none;
  }
}

/* Where fingers are the input device, every control clears 44px. On mouse
   pointers the tighter nav rhythm is kept — 41px already clears WCAG 2.5.8. */
@media (pointer: coarse) {
  /* Apple's artwork is 36px tall and may not be resized or reshaped, so the
     link grows around it instead. */
  .hero-apps-link {
    align-items: center;
    min-height: 44px;
  }
  .nav-links > a,
  .footer-col a,
  .brand { display: flex; align-items: center; min-height: 44px; }
  .lang-btn { min-height: 44px; }
}

/* Height alone was not enough: the pills were 40px across, and this project's
   own bar is 44 in both directions. Above 380px there is room for it; at 320px
   the header is already spending 284px on a wordmark, a segmented control and a
   menu button, and forcing the extra 6px pushed the row past the viewport.
   40x44 still clears WCAG 2.5.8's 24x24 by a wide margin, so the narrow end
   keeps the height and gives up the width. */
@media (pointer: coarse) and (min-width: 381px) {
  /* 98, not 96: the track is border-box and carries a 1px border each side, so
     96 left the pills at 43. */
  .lang-toggle { width: 98px; }
  .lang-btn { width: 44px; }
  .lang-slider { width: 44px; }
  .lang-toggle.is-es .lang-slider { transform: translateX(46px); }
}

/* Touch has no hover, but iOS resolves :hover on tap and leaves it stuck until
   you tap something else. Anything that moves on hover is therefore gated to
   pointers that can actually hover; touch gets a press state instead, which is
   the feedback a finger is looking for. */
@media (hover: none) {
  .btn-primary:hover,
  .btn-ghost:hover,
  .split-grid li:hover,
  .step:hover,
  .sc-tab:hover,
  .product-demo-cover:hover .product-demo-play {
    transform: none;
  }
  .btn-primary:hover svg,
  .sc-tab:hover .sc-tab-ic { transform: none; }
  .step:hover::before { opacity: 0.48; transform: translate3d(22px, -18px, 0); }
  .split-grid li:hover,
  .sc-tab:hover { box-shadow: inset 0 1px 0 rgb(var(--surface-rgb) / 0.045); }

  .btn:active { transform: scale(0.975); }
  .sc-tab:active { background: rgba(255, 255, 255, 0.05); }
  .faq-item summary:active { color: var(--accent-soft); }
  .product-demo-cover:active .product-demo-play { transform: scale(0.94); }
}

/* Landscape puts the notch and the rounded corners on the long edge, where they
   cut into the text column rather than the status bar. The viewport meta already
   opts into `viewport-fit=cover`, so the insets are non-zero and worth honouring;
   in portrait they resolve to 0 and these collapse back to --gutter. */
@media (orientation: landscape) {
  .wrap,
  .nav-inner {
    padding-left: max(var(--gutter), env(safe-area-inset-left));
    padding-right: max(var(--gutter), env(safe-area-inset-right));
  }
  .mcta-bar {
    padding-left: max(16px, env(safe-area-inset-left));
    padding-right: max(16px, env(safe-area-inset-right));
  }
}

/* Only the mobile sheet needs the inset padding. A desktop is "landscape" too,
   and there .nav-links is the centred menu, where 20px a side is just dead air. */
@media (orientation: landscape) and (max-width: 920px) {
  .nav-links {
    padding-left: max(20px, env(safe-area-inset-left));
    padding-right: max(20px, env(safe-area-inset-right));
  }
}

@media print {
  .nav, .mcta-bar, .skip-link { display: none !important; }
}
