/* GENERATED by build.py from styles/, shell/footcss.css and sections/. Do not edit. */
/* onKlockIO — tokens and type scale.
   SINGLE SOURCE OF TRUTH. Read-only for section work: consume these, never
   redefine a colour or a size locally. build.py writes it into public/site.css
   (the home page) and public/pages.css (privacy, support, android). */


:root {
  /* One ground for the whole site: black, in both themes. There is no light
     mode any more, so nothing below needs a second palette. */
  color-scheme: dark;      /* UA surfaces follow the page */
  --paper:      #000000;
  --paper-2:    #0e1214;   /* raised surface — cards, table headers */
  --ink:        #ffffff;
  --muted:      #9aa0aa;   /* 8.0:1 on black */
  --mid:        #787b83;   /* 5.0:1 — rules and large text */
  --rule:       #232829;
  --blue:       #0394c8;
  --blue-lt:    #35b3e0;
  --blue-ink:   #3ab5e4;   /* links and accents on black — 8.9:1 */
  --hot:        #e16446;
  --hot-lt:     #f08265;
  --hot-ink:    #f08265;   /* 8.1:1 on black */
  /* Lightened from the raw brand pair. #0394c8 -> #e16446 crosses a grey-blue
     midpoint (#7c7c87, 4.88:1) that showed as a muddy band through the middle
     of a long gradient word like stopTimer. These two stops are the same hues
     lifted: 8.58:1 and 8.09:1 on black, midpoint #929aa2 at 6.97:1. */
  /* 100deg, not 135: on a short wide word the gradient box's diagonal is much
     longer than the text, so a 135deg ramp puts the text in the middle of it
     and the end stops never appear. Measured on "logjams" at 135deg the violet
     arrived as #7e96ed and the orange as #8e5e56 — neither end read. At 100deg
     the gradient line is 212px across a 206px word, so the text samples very
     nearly the whole ramp. The accent bars on privacy/support use the same
     token and are wide and short, where 100deg is if anything cleaner. */
  --grad:       linear-gradient(100deg, #35b3e0, #f08265);

  --pad:        clamp(1.25rem, 5vw, 6rem);
  --nav-h:      52px;
  --shadow:     0 24px 60px rgba(0, 0, 0, .6);
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
body {
  margin: 0; background: var(--paper); color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", system-ui, sans-serif;
  font-size: clamp(17px, 1.15vw, 21px); line-height: 1.5;
  /* the glows deliberately spill past their tiles; clip (not hidden) keeps that
     off the horizontal scrollbar without making the body a scroll container,
     which would break the sticky sub-nav */
  overflow-x: clip;
}
img { max-width: 100%; height: auto; display: block; }
a { color: var(--blue-ink); text-underline-offset: .2em; }
:focus-visible { outline: 2px solid var(--blue-ink); outline-offset: 4px; border-radius: 4px; }
h1, h2, h3 { font-weight: 650; letter-spacing: -.02em; line-height: 1.05; margin: 0; text-wrap: balance; }
h2 { font-size: clamp(2.1rem, 5.2vw, 4.2rem); }
h3 { font-size: clamp(1.3rem, 2.1vw, 1.8rem); letter-spacing: -.015em; }
p { margin: 0; max-width: 60ch; }
.lede { font-size: clamp(1.1rem, 1.7vw, 1.4rem); color: var(--muted); max-width: 44ch; }
section { padding: clamp(5rem, 14vh, 10rem) var(--pad); }
.wrap { max-width: 1180px; margin: 0 auto; }
.skip { position: absolute; left: -9999px; background: var(--paper); color: var(--ink); padding: 1rem; z-index: 40; }
.skip:focus { left: 1rem; top: 1rem; }
section[id], main [id] { scroll-margin-top: calc(var(--nav-h) + 16px); }
/* onKlockIO — shared components.
   SINGLE SOURCE OF TRUTH. Used by more than one section: the reveal, the sticky
   sub-nav, the dark ground, the ambient glow, the device frames, the phone
   body, the pin-through stage, the App Store badge. Read-only for section work.
   A section that needs a phone or a glow consumes these; it does not write its
   own. build.py writes it into public/site.css. */

/* ---------- reveal ---------- */
.rise { opacity: 0; transform: translateY(28px); transition: opacity .8s ease, transform .8s cubic-bezier(.2,.7,.3,1); }
.rise.in { opacity: 1; transform: none; }
/* the device teases in from below rather than simply fading */
.rise-up { opacity: 0; transform: translateY(120px); transition: opacity 1s ease, transform 1.1s cubic-bezier(.2,.7,.25,1); }
.rise-up.in { opacity: 1; transform: none; }

/* ---------- sticky sub-nav ---------- */
.subnav {
  position: sticky; top: 0; z-index: 30;
  background: var(--paper); border-bottom: 1px solid var(--rule);
  padding: 0 clamp(1.25rem, 4vw, 2.5rem);
  transition: background .25s ease, border-color .25s ease;
}
/* pinned to the corner rather than to the centred content column */
.subnav ul { display: flex; gap: clamp(1.25rem, 3vw, 2.5rem); align-items: center; list-style: none; margin: 0 auto 0 0; padding: 0; height: var(--nav-h); overflow-x: auto; scrollbar-width: none; }
.subnav ul::-webkit-scrollbar { display: none; }
.subnav a { color: var(--muted); text-decoration: none; white-space: nowrap; font-size: .95rem; letter-spacing: -.01em; }
.subnav a[aria-current="true"], .subnav a[aria-current="page"] { color: var(--ink); }
/* At the top the bar carries the hero's own black, so it reads as part of it
   rather than as a strip above it. It swaps to the page ground as soon as the
   hero has scrolled past, so the links stay readable over the light sections.
   (Transparent would not do: the body ground behind it is white.) */
.over-hero { background: #000; border-bottom-color: transparent; }
.over-hero a { color: rgba(255, 255, 255, .62); }
.over-hero a[aria-current="true"] { color: #fff; }
.over-hero.solid { background: var(--paper); border-bottom-color: var(--rule); }
.over-hero.solid a { color: var(--muted); }
.over-hero.solid a[aria-current="true"] { color: var(--ink); }

/* ---------- dark sections ---------- */
.dark { background: var(--paper); color: var(--ink); }   /* no seam: one ground */
/* every section shares that ground, so no adjacency needs a doubled gap */
main > section + section { padding-top: 0; }
.dark h2, .dark h3 { color: #fff; }
.dark .lede { color: rgba(255, 255, 255, .74); }
.dark a { color: #7fd0ef; }

/* ---------- ambient glow ----------
   Two to four colours sampled from the screenshot itself, each blurred on its
   own and dropped off-centre, so the light falls unevenly like bounced light
   rather than one centred radial. Solid shapes and filter: blur only — no
   backdrop-filter, so prefers-reduced-transparency has nothing to strip. */
.glow { position: absolute; inset: -18% -12%; z-index: 0; pointer-events: none; }
.glow i {
  position: absolute; left: var(--x); top: var(--y);
  width: var(--s); aspect-ratio: 1; border-radius: 50%;
  background: var(--c); filter: blur(var(--b, 74px));
  opacity: var(--o, .55); transform: translate(-50%, -50%);
}

/* ---------- device frames ---------- */
.device { position: relative; width: min(100%, var(--dev-w, 300px)); margin: 0 auto; }
.device .bezel { position: relative; z-index: 1; background: #0a0a0c; padding: 9px; border-radius: 44px; box-shadow: 0 0 0 1px rgba(255,255,255,.12), 0 28px 56px rgba(0,0,0,.5); }
.device .bezel img { width: 100%; border-radius: 36px; }
.device .island { position: absolute; z-index: 2; top: 17px; left: 50%; transform: translateX(-50%); width: 30%; height: 22px; background: #000; border-radius: 999px; }
.device.sm { --dev-w: 236px; }
.device.sm .bezel { padding: 7px; border-radius: 34px; }
.device.sm .bezel img { border-radius: 28px; }
.device.sm .island { top: 13px; height: 17px; }
.device.pad .bezel { padding: 13px; border-radius: 26px; }
.device.pad .bezel img { border-radius: 14px; }

/* ---------- gradient headline ----------
   Put the span around the WORD only; punctuation stays outside so it renders
   in --ink. */
.gradient-text {
  background-clip: text; -webkit-background-clip: text;
  color: transparent; background-image: var(--grad);
}

/* ---------- a phone with actual body ----------
   Not a rounded rectangle with a border: a titanium rail with a real gradient
   round it, a screen inset inside that rail, and a bottom wall folded back in
   3D so the handset has thickness you can see when it is tilted. Everything is
   CSS — there is no rendered 3D model here and none is claimed. */
.iphone {
  --w: 320px;
  --rail: calc(var(--w) * .026);
  --thick: calc(var(--w) * .075);
  width: var(--w); aspect-ratio: 359 / 780;
  position: relative; transform-style: preserve-3d;
}
.iphone .rail {
  position: absolute; inset: 0; border-radius: calc(var(--w) * .138);
  transform-style: preserve-3d;
  /* brushed metal: the bright bands are where a rail catches the light */
  background: linear-gradient(102deg,
    #55595d 0%, #b9bec3 7%, #7e8388 17%, #4d5155 33%,
    #9aa0a5 52%, #6b7075 68%, #c3c8cc 84%, #52565a 94%, #3d4145 100%);
  box-shadow:
    inset 0 .5px 0 rgba(255,255,255,.55),
    inset 0 -.5px 0 rgba(255,255,255,.18),
    0 40px 70px rgba(0,0,0,.72);
}
/* the bottom edge, folded back so it reads as the handset's depth */
.iphone .rail::after {
  content: ""; position: absolute; left: 2.5%; right: 2.5%; bottom: 0;
  height: var(--thick); transform-origin: top center; transform: rotateX(-90deg);
  border-radius: 0 0 calc(var(--w) * .05) calc(var(--w) * .05);
  background: linear-gradient(to bottom, #6a6f74, #35393d 45%, #17191c);
}
.iphone .glass {
  position: absolute; inset: var(--rail);
  border-radius: calc(var(--w) * .118); overflow: hidden;
  background: #000; box-shadow: inset 0 0 0 1px rgba(0,0,0,.9);
}
.iphone .glass img { width: 100%; display: block; }
.iphone .island {
  position: absolute; z-index: 2; top: calc(var(--w) * .038); left: 50%;
  transform: translateX(-50%); width: 30%; height: calc(var(--w) * .062);
  background: #000; border-radius: 999px;
}
/* the sheen across the glass, and the contact shadow under the handset */
.iphone .sheen {
  position: absolute; inset: var(--rail); z-index: 3; pointer-events: none;
  border-radius: calc(var(--w) * .118);
  background: linear-gradient(102deg, rgba(255,255,255,.10) 0 18%, rgba(255,255,255,0) 42%);
}

/* ---------- pin-through ----------
   The stage sticks for the whole height of its container, so the viewport holds
   still while the beats change. Progress is read from scroll position; nothing
   here intercepts a scroll, wheel or touch event. */
.pin { position: relative; }
.pin-stage {
  position: sticky; top: 0; height: 100svh;
  display: grid; align-content: center;
  padding: calc(var(--nav-h) + clamp(1rem, 3vh, 2rem)) var(--pad) clamp(1rem, 3vh, 2rem);
  overflow: hidden;
}
.beat { grid-area: 1 / 1; }

/* ---------- App Store badge ---------- */
.badge { display: inline-flex; align-items: center; gap: .7rem; background: var(--ink); color: var(--paper); padding: .75rem 1.4rem .8rem; border-radius: 12px; text-decoration: none; font-weight: 500; }
.badge svg { width: 22px; height: 26px; fill: currentColor; flex: none; }
.badge span { display: block; line-height: 1.1; text-align: left; }
.badge .sm { font-size: .62em; opacity: .82; }
.badge .lg { font-size: 1.05em; letter-spacing: -.01em; }

/* ---------- stat grid ----------
   The huge-numeral treatment. Used by more than one section (the arithmetic in
   Section 2 and the stat grid), so the base lives here and sections only
   override sizes under their own scope. .s1-.s4 traverse the brand run. */
.stats { display: grid; gap: clamp(3rem, 7vw, 6rem); grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); margin-top: clamp(3rem, 8vh, 6rem); }
.stats dl, .stat dl { margin: 0; }
.stat dt, .stat dd { margin: 0; }
.stat b { display: block; font-size: clamp(3.2rem, 8vw, 6rem); font-weight: 650; letter-spacing: -.045em; line-height: .92; font-variant-numeric: tabular-nums; }
.stat span { display: block; margin-top: .9rem; color: var(--muted); font-size: .95rem; max-width: 22ch; }
.s1 b { color: var(--blue); } .s2 b { color: var(--blue-lt); } .s3 b { color: var(--hot-lt); } .s4 b { color: var(--hot); }

/* A pinned section carries no padding of its own — its height IS the scroll
   runway — so the section that follows one has to get its top padding back,
   or its first line butts straight onto the released stage. */
.pin + section { padding-top: clamp(5rem, 14vh, 10rem); }

/* ---------- pin-through: reduced motion ----------
   The shared counterpart to .pin/.pin-stage above. A section that pins must
   unpin here too; its own file is still responsible for making its individual
   beats visible, because the JS stands down and writes no progress values. */
@media (prefers-reduced-motion: reduce) {
  .pin { height: auto; }
  .pin-stage {
    position: static; height: auto; overflow: visible;
    display: block; padding: 0 var(--pad);
  }
  .beat { grid-area: auto; }
}

/* ---------- global motion preference ----------
   Page-level, so it lives with the shared layer rather than in any one section.
   Individual sections still own the rules that make THEIR beats visible. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { animation: none !important; transition: none !important; }
  .rise, .rise-up { opacity: 1; transform: none; }
}
/* ---------- footnotes + footer ---------- */
.footnotes { padding: 0 var(--pad) clamp(2.5rem, 6vh, 4rem); }
.footnotes h2 { font-size: .8rem; letter-spacing: .14em; text-transform: uppercase; color: var(--muted); margin-bottom: 1.1rem; font-weight: 600; }
.footnotes ol { max-width: 1180px; margin: 0 auto; padding-left: 1.3rem; color: var(--muted); font-size: .85rem; }
.footnotes li { margin-bottom: .6rem; max-width: 70ch; }
.footnotes a { color: var(--muted); }
.fn-ref { font-size: .62em; vertical-align: super; text-decoration: none; font-weight: 600; padding: 0 .1em; }
footer { border-top: 1px solid var(--rule); padding: clamp(3rem, 8vh, 5rem) var(--pad); }
.foot-grid { display: grid; gap: 2.5rem; align-items: start; }
@media (min-width: 760px) { .foot-grid { grid-template-columns: auto 1fr; } }
.foot-links { display: flex; flex-wrap: wrap; gap: 1.5rem; }
@media (min-width: 760px) { .foot-links { justify-content: flex-end; } }
.foot-links a { color: var(--muted); text-decoration: none; }
.foot-links a:hover { color: var(--ink); }
.legal { color: var(--muted); font-size: .85rem; max-width: 62ch; margin-top: 2.5rem; }

/* ==================== 01-hero ==================== */
/* ---------- hero ----------
   Title, then the line, then the product — the shape of Apple's own iPad Pro
   page. The device is capped against viewport height so the whole thing lands
   on one screen instead of pushing the badge below the fold. */
.hero {
  min-height: calc(100svh - var(--nav-h)); display: grid; grid-template-rows: 1fr auto;
  justify-items: center; text-align: center;
  padding: clamp(2.5rem, 7vh, 5rem) var(--pad) 0;
  background: #000; color: #fff;
}
.hero-inner { display: grid; justify-items: center; gap: clamp(1.1rem, 3vh, 2rem); align-content: center; }
.wordmark {
  font-size: clamp(2.75rem, 8.5vw, 7rem); font-weight: 650;
  letter-spacing: -.035em; line-height: 1; margin: 0;
  display: flex; align-items: center; justify-content: center; gap: .26em;
}
/* sized in em so the icon tracks the type: its box matches the font size, which
   puts its optical width alongside the cap height of the wordmark */
.wordmark .mark { width: .92em; height: .92em; border-radius: 22.4%; flex: none; }
.wordmark .dot { color: rgba(255, 255, 255, .55); }
.hero-device { position: relative; margin-top: clamp(.5rem, 2vh, 1.5rem); perspective: 1500px; }
.hero-device .device {
  --dev-w: min(1100px, 96vw, calc(46svh * 1.33));
  position: relative; z-index: 1;
  /* laid back 25°, so you are looking down onto the screen rather than at it */
  transform: rotateX(25deg);
  transform-origin: 50% 58%;
}
.hero-device .device.pad .bezel { padding: 12px; border-radius: 24px; }
.hero-device .device.pad .bezel img { border-radius: 14px; }
/* The brand gradient split across the two fragments, the way Apple splits a
   colour run over "Mmmmm." and "Power.". The hero ground is always black, so
   both stops are the bright ends — 8:1 and better against it. */
.tagline {
  /* floor raised from 1.375rem: at 22px the duotone's darkest stops (3.8:1)
     would need 4.5:1, since weight 650 is not "bold" for WCAG. 24px is large
     text at any weight, where the 3:1 bar applies and they clear it. */
  font-size: clamp(1.5rem, 4.25vw, 3.5rem);
  font-weight: 650;
  letter-spacing: -.03em; line-height: 1.1; max-width: none; text-wrap: balance;
}
/* Duotone rather than a flat two-colour split: each fragment runs deep to light
   across itself, and the hue drifts with the lightness — the blue cools toward
   cyan as it brightens, the red warms toward peach — so the two halves read as
   the ends of one gradient rather than two solid swatches. Darkest stops are
   3.8:1 and 3.9:1 on black, past the 3:1 bar for display type. */
.tagline span { background-clip: text; -webkit-background-clip: text; color: transparent; }
.tagline .f1 { background-image: linear-gradient(100deg, #1b6ea8 0%, #0f9ecf 52%, #6fd9f5 100%); }
.tagline .f3 { background-image: linear-gradient(100deg, #b8452b 0%, #e16446 52%, #f9b18c 100%); }
.hero .badge { background: #fff; color: #000; }
.cue { margin-top: clamp(1rem, 3vh, 2rem); padding-bottom: 3rem; color: rgba(255, 255, 255, .55); font-size: .8rem; letter-spacing: .14em; text-transform: uppercase; display: grid; justify-items: center; gap: .6rem; }
.cue svg { width: 14px; animation: bob 2.4s ease-in-out infinite; }
@keyframes bob { 0%,100% { transform: translateY(0) } 50% { transform: translateY(6px) } }

/* glow colours as classes, not style="" attributes: the CSP allows no inline style */
.glow .glow-hero-1 { --c:#1b6ea8; --x:26%; --y:52%; --s:48%; --b:104px; --o:.34; }
.glow .glow-hero-2 { --c:#6fd9f5; --x:38%; --y:34%; --s:30%; --b:88px; --o:.16; }
.glow .glow-hero-3 { --c:#b8452b; --x:76%; --y:60%; --s:44%; --b:104px; --o:.3; }
.glow .glow-hero-4 { --c:#f9b18c; --x:64%; --y:38%; --s:26%; --b:88px; --o:.14; }

/* ==================== 02-honest ==================== */
/* ---------- 2: an honest number ----------
   No screenshot here, so no glow: nothing in this section needs a light source. */
.honest h2 { font-size: clamp(2.25rem, 6vw, 5rem); letter-spacing: -.03em; line-height: 1.04; max-width: 17ch; }
/* one continuous sweep across the whole line rather than a per-fragment split:
   the desaturated blue-to-rose from the sample, which is the brand gradient
   pulled back. On the black ground the two stops measure 9.2:1 and 8.8:1. */
.honest .gradient-text {
  /* the sample is a light-mode crop; on black its mid-tones go muddy, so this
     is the same two hues lifted to the new ground */
  background-image: linear-gradient(100deg, #7fb4c4, #d29a94);
  background-clip: text; -webkit-background-clip: text; color: transparent;
}
.honest-grid { display: grid; gap: clamp(2.5rem, 5vw, 4rem); margin-top: clamp(3rem, 8vh, 5rem); }
@media (min-width: 940px) { .honest-grid { grid-template-columns: 1.45fr .55fr; align-items: center; } }

/* the "whole arithmetic" treatment, run once per exam: huge coloured numeral,
   plain grey caption, no icons, no chrome */
.arith { display: grid; gap: clamp(2.25rem, 5vh, 3.5rem); }
.exam-name {
  font-size: .8rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--muted); font-weight: 600; margin-bottom: clamp(1rem, 2vh, 1.5rem);
}
.arith .stats {
  margin-top: 0; gap: clamp(1.25rem, 3vw, 2.5rem);
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
}
.arith .stat b { font-size: clamp(1.9rem, 4.2vw, 3.2rem); }
.arith .stat span { margin-top: .5rem; font-size: .84rem; max-width: 17ch; }
.arith-note { color: var(--muted); font-size: .88rem; max-width: 52ch; }

/* a faithful rebuild of the app's own Avg / question tile, not a screenshot —
   it stays crisp at any size and the numbers stay selectable text */
.avg-wrap { display: grid; gap: 1.15rem; justify-items: start; }
.avg-card {
  width: min(100%, 330px); background: #fff; border-radius: 22px;
  padding: 1.2rem 1.35rem 1.4rem; display: grid; gap: .55rem; justify-items: start;
  box-shadow: 0 22px 55px rgba(0, 0, 0, .55);
}
.avg-card p { max-width: none; }
.avg-lab { display: flex; align-items: center; gap: .45rem; color: #6e6e73; font-size: .95rem; }
.avg-lab svg { width: 17px; height: 17px; stroke: #8a8a8e; fill: none; stroke-width: 1.6; flex: none; }
.avg-val {
  font-size: clamp(2.1rem, 3.4vw, 2.7rem); font-weight: 680; letter-spacing: -.03em;
  color: #1d1d1f; line-height: 1; font-variant-numeric: tabular-nums;
}
.avg-delta {
  display: inline-flex; align-items: center; gap: .3rem;
  background: #f6e8ca; color: #7a5c14; border-radius: 999px;
  padding: .28rem .7rem .32rem; font-size: .92rem; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.avg-note { color: var(--muted); font-size: .92rem; max-width: 32ch; }

/* ==================== 03-04-stoptimer ==================== */
/* ---------- 3+4: stopTimer, whole-object scale and slide ----------
   The framed handset is ONE object. Progress drives scale() and translateX()
   on that object together: at 0 it is ~1.62x and centred, as if held up to the
   viewer; at 1 it is 1x and settled left. The frame is present at every point —
   this is not a crop into the screenshot and there is no clipping window
   anywhere in this section.

   Reuses the shared .iphone frame from components.css (rail / glass / island /
   sheen). That component is used rather than .device because its rail and radii
   are proportional to --w, so they stay correct as the object scales; .device's
   bezel is a fixed 9px and would visibly thin out when enlarged. */
/* ── THE SCREEN CAPTURE ────────────────────────────────────────────────
   Swapping the asset is THIS ONE LINE and nothing else. The screen is a
   background layer rather than an <img> precisely so the path lives in one
   declared place; it keeps its accessible name from role="img" + aria-label
   on the element below.
   Current file: shots/09-running-hi.webp, 2640 x 5736. At progress 0 the screen
   renders 1297 device pixels wide, so the asset is 2.035x that — the headroom
   the old 1320 x 2868 JPEG never had, and the reason it now reads sharp.
   WebP q92: 131 KB against 661 KB lossless, 0.162/255 mean error, and since it
   is downsampled 2:1 on the way to the screen that error is halved again. The
   lossless master lives outside the site at Screenshots/launch/iphone-6.9-inch/.
   Aspect is 0.46025, the same as the capture it replaced, so the glass box
   needs no change. */
.seq { --shot: url("shots/09-running-hi.webp"); }

.seq { height: 300svh; padding: 0; }
.seq .pin-stage { grid-template-columns: minmax(0, 1fr); align-content: center; padding: 0; }
/* full-bleed AND full-height: the stage's clip box is now exactly the viewport,
   so the glow has no rectangular edge of its own to terminate against */
.seq-stage { overflow: hidden; }
.seq-copy { padding-inline: var(--pad); }

.seq-copy {
  position: absolute; z-index: 4; top: calc(var(--nav-h) + clamp(1rem, 4vh, 3rem));
  left: 50%; transform: translateX(-50%);
  width: min(100%, 52rem); text-align: center; opacity: var(--copy, 0);
}
/* one continuous headline. Sized so 21 characters clear the gutters at 320px
   (1.4rem x ~10.2em = 228px inside a 280px column) and cap at 4.4rem. */
.seq-copy h2 { font-size: clamp(1.4rem, 7.2vw, 4.4rem); white-space: nowrap; }
.seq-copy .lede { margin: clamp(1rem, 2.5vh, 1.75rem) auto 0; max-width: 32ch; }

/* definite height: the children are absolutely positioned, so a percentage
   height on an auto-sized grid row would resolve to zero */
.seq-stage { position: relative; height: 100svh; }

/* genuine 3D: the perspective lives on the parent, the rotation on the object */
/* perspective creates a stacking context, so the device z-index is scoped
   inside here — the wrappers themselves have to be ranked */
/* The vertical stretch was the projection, not the image: the rendered screen
   box measures 0.46071 against the capture's own 0.46025, so nothing is being
   scaled non-uniformly. It came from two compounding terms.

   dy/dd = cos(t) * P/(P-z)  +  (y - oy) * P * sin(t) / (P-z)^2

   The first is the depth divide — with transform-origin at the TOP edge every
   visible part sits below the pivot and swings toward the camera, so it grows
   rather than foreshortens. The second is the killer: perspective-origin was
   50% 10%, roughly 540px above the ring, and that offset multiplies straight
   into the vertical rate while leaving the horizontal rate (P/(P-z), which is
   independent of the origin) untouched. It alone accounted for about +6.6% of
   the measured +8.9%.

   Origin at 50% 50% zeroes most of (y - oy), and 1600px softens the divide.
   Predicted ring at progress 0: 1.0607 horizontal against 1.0558 vertical —
   half a percent SQUASHED, which is what a tilt should do. */
.seq-persp { position: absolute; inset: 0; z-index: 2; perspective: 1600px; perspective-origin: 50% 50%; }
/* ONE axis. rotateX only — top edge tilted away, pivoting on the top edge so
   the status bar stays anchored under the copy while the body leans back.
   rotateY and rotateZ are never written, at any progress. */
.seq-device {
  position: absolute; top: 0; left: 50%; z-index: 2;
  transform-style: preserve-3d;
  transform-origin: 50% 0%;
  transform:
    translateX(calc(-50% + var(--slidep, 1) * var(--slide, 0px)))
    translateY(calc(var(--y0, 20svh) + (var(--y1, 11svh) - var(--y0, 20svh)) * var(--p, 1)))
    rotateX(calc((1 - var(--p, 1)) * var(--rotx, 15deg)))
    scale(calc(var(--s0, 1) + (var(--s1, 1) - var(--s0, 1)) * var(--p, 1)));
}
/* --w is the LAYOUT width, and it is the LARGEST the handset ever renders.
   This is the fix for the soft screen. A transformed element is rasterised
   once at its layout size and the transform then samples that raster, so the
   old arrangement — lay out at the progress-1 size, scale UP 2x at progress 0 —
   was magnifying a small raster however sharp the source asset was. Laying out
   at the big size and scaling DOWN means every progress value samples at or
   below 1:1, and progress 1 is supersampled into the bargain. */
.seq-device .iphone { --w: var(--w-layout, 300px); aspect-ratio: 359 / 745.885; }
/* The rail is no longer uniform (see "Rail thickness" below): 3.2% on the
   sides, 2.2% top and bottom. With the outer box fixed you cannot widen the
   side rail and hold the screen's aspect — a wider side rail narrows the glass,
   which needs a SHORTER glass to keep the ratio — so the outer ratio is
   re-derived instead of the crop being absorbed:
     glass width   = 1 - 2(.032)            = .936 of --w
     glass height  = .936 / (1320/2868)     = 2.033673 of --w
     outer height  = 2.033673 + 2(.022)     = 2.077673  ->  359 / 745.885
   The glass box is therefore 0.460251 against a capture of 1320x2868 =
   0.460251 — exact in the derivation. Measured in the browser at --w 439.9px
   it lands at 0.460335, a 0.018% trim: that residue is integer-pixel rounding
   of the rendered box, not the geometry, and it is the same order as the
   0.017% the uniform-rail version carried. Nothing is stretched to fit. */
.seq-device .iphone .glass .shot {
  position: absolute; inset: 0;
  background-image: var(--shot);
  background-size: cover;
  background-position: center top;
}

/* ---- the Dynamic Island belongs to the SCREEN, not the frame ----
   It was a child of .rail with `top: calc(var(--w) * .038)`, measured from the
   FRAME's top edge — which put it 6.2px below the screen's top edge at
   progress 0, grazing the bezel. It is now a child of .glass and every number
   is taken from the real handset: an iPhone 16 Pro Max screen is 440 x 956pt
   and its island is 125 x 37.33pt sitting 11pt below the screen's top edge.
   The glass is .936 of --w wide (3.2% side rail), so as fractions of --w:
     top     11 / 440   = 2.500% of the screen  ->  .936 * .02500 = .02340
     height  37.33 / 440 = 8.484% of the screen ->  .936 * .08484 = .07941
   Width stays a plain percentage because it already resolves against the
   screen. left: 50% / translateX(-50%) come from the shared component and are
   correct once the parent is the screen. */
.seq-device .iphone .glass .island {
  z-index: 2;
  top: calc(var(--w) * .0234);
  width: 28.41%;
  height: calc(var(--w) * .07941);
}

/* the sampled glow, following the phone but never rotating with it */
.seq-glow {
  position: absolute; top: 0; left: 50%; z-index: 1; pointer-events: none;
  width: var(--w-layout, 300px); aspect-ratio: 359 / 745.885;
  transform:
    translateX(calc(-50% + var(--slidep, 1) * var(--slide, 0px)))
    translateY(calc(var(--y0, 20svh) + (var(--y1, 11svh) - var(--y0, 20svh)) * var(--p, 1)))
    scale(calc(var(--s0, 1) + (var(--s1, 1) - var(--s0, 1)) * var(--p, 1)));
}


/* Frame material. Four layers, all on the rail, none on the screenshot.
   1. Edge highlights — 1px bright along the top and left, near-gone along the
      bottom and right. This is the one that carries the realism.
   2. Rail definition — a darker band just inside the outer edge, so the metal
      reads as something with thickness rather than a drawn outline.
   3. The contact shadow, still answering the rotation: thrown long and soft
      while the handset is turned, tightening under it as it comes flat. */
.seq-device .iphone .rail {
  /* Band 2 of 3, the body: the shared component's brushed silver remapped into
     the navy-charcoal family. Same band rhythm — the bright bands are still
     where a rail catches the light — but compressed dark, so the lightest band
     is #3b4859 and the white app UI behind the glass stays the brightest thing
     in the section. Bands 1 and 3 (outer specular edge, inner dark edge) are
     the first and fifth insets below. */
  background: linear-gradient(102deg,
    #1a222c 0%, #38455a 7%, #232d3a 17%, #161d26 33%,
    #2f3b4b 52%, #1e2733 68%, #3b4859 84%, #19212b 94%, #131a23 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .62),
    inset 1px 0 0 rgba(255, 255, 255, .42),
    inset 0 -1px 0 rgba(255, 255, 255, .10),
    inset -1px 0 0 rgba(255, 255, 255, .07),
    inset 0 0 0 calc(var(--w) * .0065) rgba(0, 0, 0, .66),
    0px
    calc(24px + var(--rotn, 0) * 40px)
    calc(40px + var(--rotn, 0) * 70px)
    rgba(0, 0, 0, calc(.45 + var(--rotn, 0) * .25));
}
/* The folded bottom edge is shared and still silver. It has to follow the body
   into navy, or the handset's one piece of visible depth belongs to a different
   phone than the rest of it. */
.seq-device .iphone .rail::after {
  background: linear-gradient(to bottom, #2b3543, #161d26 45%, #090c10);
}

/* ---- Rail thickness, asymmetric as on real hardware ----
   The shared component insets the screen by a uniform --rail (2.6%). Real
   handsets carry a wider band down the sides than across the top and bottom,
   so this splits it: 2.2% top/bottom, 3.2% left/right. The outer ratio above
   is re-derived to keep the capture fitting exactly.

   Radii stay CONCENTRIC rather than identical. The frame's outer radius is
   13.8% of --w, so the screen's is 13.8% - 3.2% = 10.6%: the two curves run
   parallel at a constant 3.2% offset, which is what makes a frame read as
   milled rather than as two rounded rectangles stacked. */
.seq-device .iphone .glass,
.seq-device .iphone .sheen {
  inset: calc(var(--w) * .022) calc(var(--w) * .032);
  border-radius: calc(var(--w) * .106);
}
/* A thin dark bezel between rail and screen, so the display sits INSIDE the
   frame instead of flush with it. Outward shadow, so it spends rail pixels and
   never crops the capture. */
.seq-device .iphone .glass {
  box-shadow:
    0 0 0 calc(var(--w) * .005) #05070a,
    inset 0 0 0 1px rgba(0, 0, 0, .9);
}

/* ---- Hardware buttons ----
   Drawn on .grain's pseudo-elements rather than added to the markup: they are
   decoration with no semantics, and the section already owns .grain. Each sits
   just proud of the rail and is sized off --w, so it scales with the pinned
   zoom like everything else on the handset. Low contrast on purpose — enough
   to read as hardware, not enough to compete with the screen.
     left  — action button (short), then volume up and volume down
     right — power, sitting lower than the volume pair, as on the real thing
   The bands have square ends: at a 2.7px button width the radius is not
   resolvable, and rounding each one costs three more elements. */
.seq-device .iphone .rail .grain::before,
.seq-device .iphone .rail .grain::after {
  content: ""; position: absolute; pointer-events: none;
  width: calc(var(--w) * .009);
}
.seq-device .iphone .rail .grain::before {
  left: calc(var(--w) * -.006); top: 0; bottom: 0;
  background:
    linear-gradient(90deg, #333f50, #141b24) 0 19%   / 100% 4.4% no-repeat,
    linear-gradient(90deg, #333f50, #141b24) 0 28.5% / 100% 8.2% no-repeat,
    linear-gradient(90deg, #333f50, #141b24) 0 39%   / 100% 8.2% no-repeat;
}
.seq-device .iphone .rail .grain::after {
  right: calc(var(--w) * -.006); top: 31%;
  height: calc(var(--w) * .105);
  background: linear-gradient(90deg, #141b24, #333f50);
}

/* Brushed titanium, as a real asset rather than an approximation. A 240x8
   RGBA tile, generated per column at three frequencies — wide polish bands,
   brush strokes, single-pixel scratches — with a little wrapped wobble down
   each stroke so it tiles seamlessly. Mid-tone is fully TRANSPARENT and only
   the deviations carry alpha (18% at the brightest), so it needs no blend mode
   and cannot flatten the 3D context: it simply modulates the rail gradient
   underneath. background-size is in px, so the grain scales with the object's
   own transform — the brushing gets physically bigger as the phone does,
   which is what a real surface does.

   The one thing this does not do: an iPhone's rail is brushed ALONG its
   perimeter, so the top and bottom bands should run horizontally and only the
   sides vertically. One tile cannot do both, and at a rail band of 12px at
   rest the direction is not resolvable — so the grain runs vertically
   throughout. Four separately-rotated edge elements would fix it and are not
   worth the markup. */
.seq-device .iphone .rail .grain {
  position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  background: url("shots/grain.png") repeat;
  background-size: 120px 4px;
}
/* 4. The specular sweep: a narrow band of light across the upper-left, its
   angle swinging 26 degrees as rotateX unwinds, so the highlight travels over
   the metal instead of sitting on it. Generated before .glass in paint order,
   so the screen covers it and it shows only on the rail itself. */
.seq-device .iphone .rail::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  border-radius: inherit;
  background: linear-gradient(calc(104deg - var(--rotn, 0) * 26deg),
    rgba(255, 255, 255, 0)    7%,
    rgba(255, 255, 255, .40) 11%,
    rgba(255, 255, 255, .06) 15%,
    rgba(255, 255, 255, 0)   20%);
}
/* Glass: a 4%-to-1.2% diagonal white wash. Anything stronger reads as bloom
   and starts eating the UI's own colour, which has to stay the brightest
   thing in the section. */
.seq-device .iphone .sheen {
  opacity: 1;
  background: linear-gradient(114deg,
    rgba(255, 255, 255, 0)    22%,
    rgba(255, 255, 255, .04)  34%,
    rgba(255, 255, 255, .012) 48%,
    rgba(255, 255, 255, 0)    60%);
  transform: translateX(calc(var(--rotn, 0) * -9%));
}

/* annotations — thin neutral leader lines, never brand colour */
.seq-notes { position: absolute; inset: 0; z-index: 3; opacity: var(--notes, 1); pointer-events: none; }
.seq-note { position: absolute; left: 57%; width: min(32ch, 36%); }
.seq-note b { display: block; font-size: clamp(1rem, 1.5vw, 1.25rem); color: var(--ink); }
.seq-note span { display: block; font-size: .92rem; color: var(--muted); margin-top: .25rem; }
.seq-wires {
  position: absolute; inset: 0; z-index: 3; pointer-events: none;
  opacity: var(--notes, 1); overflow: visible;
}
.seq-wires path { fill: none; stroke: var(--mid); stroke-width: 1; }
.seq-wires circle { fill: var(--mid); }
/* Each blob is a radial gradient that reaches transparent at 70% of its own
   radius, so it has no boundary anywhere — a blurred solid keeps a discernible
   edge however wide the blur. Every blob is wider than the phone and hangs well
   past it, and the container's own box is not clipped: the only clip in this
   section is the viewport. Black stays black; the glow only adds light. */
.seq-glow i {
  position: absolute; left: var(--x); top: var(--y);
  width: var(--s); aspect-ratio: 1; transform: translate(-50%, -50%);
  background: radial-gradient(circle closest-side, var(--c) 0%, transparent 70%);
  opacity: var(--o);
}
/* the ring sits at 29.3% of the screen, the module label at 46.2%, the
   stopwatch pill at 53.1% — three labels cannot align exactly to a band that
   narrow, so they are spaced for legibility and the lines carry the pointing */
.n-timer { top: 26%; }
.n-where { top: 44%; }
.n-watch { top: 58%; }

@media (max-width: 940px) {
  .seq-notes { position: absolute; inset: auto 0 0 0; display: grid; gap: .9rem; }
  .seq-note { position: static; left: auto; width: auto; }
  .seq-wires { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  /* unpinned: the flat device and every annotation at once, in normal flow */
  .seq { height: auto; padding: clamp(5rem, 14vh, 10rem) 0; }
  .seq .pin-stage { position: static; height: auto; overflow: visible; display: block; padding: 0 var(--pad); }
  .seq-copy { position: static; transform: none !important; opacity: 1 !important; margin-inline: auto; }
  .seq-stage { height: auto; margin-top: clamp(2rem, 6vh, 4rem); }
  .seq-persp { position: static; perspective: none; }
  .seq-device { position: static; transform: none !important; }
  .seq-device .iphone .sheen { transform: none; }
  .seq-device .iphone .rail::before { display: none; }
  .seq-device .iphone { --w: min(100%, 300px); margin-inline: auto; }
  .seq-notes { position: static; opacity: 1 !important; display: grid; gap: 1rem; margin: clamp(2rem,6vh,3rem) auto 0; max-width: 34rem; }
  .seq-note { position: static; left: auto; width: auto; }
  .seq-wires { display: none; }
}

/* glow colours as classes, not style="" attributes: the CSP allows no inline style */
.seq-glow .glow-stop-1 { --c:#0087cc; --x:50%; --y:62%; --s:138%; --o:.14; }
.seq-glow .glow-stop-2 { --c:#60caff; --x:20%; --y:78%; --s:102%; --o:.09; }
.seq-glow .glow-stop-3 { --c:#cc0000; --x:82%; --y:48%; --s:104%; --o:.08; }

/* ==================== 08-features ==================== */
/* ---------- 8: what is in the app ----------
   A plain list, not a pinned beat: each line names one study feature and what
   it does. Every claim is checked against the app source (Models.swift,
   SetupView.swift, SessionMetrics.swift, ProgressScreen.swift). No screenshot,
   so no glow. Same tile treatment as the closing values. */
.s-feat .feats {
  display: grid; gap: .75rem; grid-template-columns: 1fr;
  margin: clamp(2.5rem, 6vh, 4rem) 0 0; padding: 0; list-style: none;
}
@media (min-width: 620px)  { .s-feat .feats { grid-template-columns: 1fr 1fr; } }
@media (min-width: 980px)  { .s-feat .feats { grid-template-columns: 1fr 1fr 1fr; } }
.s-feat .feats li {
  border-radius: 20px; padding: clamp(1.2rem, 2vw, 1.5rem);
  background: var(--paper-2); border: 1px solid var(--rule);
}
.s-feat .feats h3 { font-size: 1.05rem; letter-spacing: -.01em; }
.s-feat .feats p { margin-top: .45rem; font-size: .93rem; line-height: 1.45; color: var(--muted); max-width: none; }

/* ==================== 07-closing ==================== */
/* ---------- 7: the closing values ----------
   No screenshot in this section, so no glow: there is no light source to
   justify one. The tiles are the raised surface and a hairline, nothing more —
   no icons, no accent bars, no per-tile colour. */
.s-vals h2 { max-width: 17ch; }
/* one continuous sweep across the whole line, the .tagline / .seq-copy .name
   idiom with the brand pair taken straight from the tokens. Both ends are the
   bright stops: 8.7:1 and 8.1:1 on black, and the sRGB mid it passes through
   bottoms out at 7.4:1 — every stop clears 4.5:1, not just the 3:1 display bar. */
.s-vals .gradient-text {
  background-image: linear-gradient(100deg, var(--blue-lt), var(--hot-lt));
  background-clip: text; -webkit-background-clip: text; color: transparent;
}
.s-vals .tiles {
  display: grid; gap: .75rem; grid-template-columns: 1fr;
  margin-top: clamp(2.5rem, 6vh, 4rem);
}
/* one column, then two, then four — never three, which would leave the fourth
   tile stranded on its own row */
@media (min-width: 620px)  { .s-vals .tiles { grid-template-columns: 1fr 1fr; } }
/* two tiles now, so 1 -> 2 and no further */
.s-vals .tile {
  position: relative; border-radius: 20px;
  padding: clamp(1.35rem, 2.2vw, 1.75rem);
  background: var(--paper-2); border: 1px solid var(--rule);
  display: flex; flex-direction: column; gap: .6rem;
}
/* the shared h3 scale is display-sized; a closing tile is not a headline */
.s-vals .tile h3 { font-size: 1.05rem; letter-spacing: -.01em; }
.s-vals .tile p { font-size: .93rem; line-height: 1.45; color: var(--muted); max-width: none; }
/* pushed to the floor of the tile so the four bottom edges agree */
.s-vals .tile .more { margin-top: auto; padding-top: .5rem; }
/* the link is the tile's only interactive element and it owns the whole tile as
   a target; its accessible name stays the four words you can actually read */
.s-vals .tile .more a::after { content: ""; position: absolute; inset: 0; }

.s-vals .badge { margin-top: clamp(1.5rem, 3.5vh, 2.25rem); }
