/* RepCheck pre-launch site — white page, black Archivo type, hairline grid.
   Cursor interactions (hero dot field, magnetic buttons) live in app.js. */

:root {
  --ink: #0a0a0a;
  --muted: #5c5c5c;
  /* 5.33:1 on --paper. Was #767676, which measured 4.54:1 -- technically
     over the 4.5 AA line but with no margin, and every use of it is
     11px uppercase mono with wide letter-spacing, which is the hardest
     case to read. Do not lighten this again. */
  --meta: #6b6b6b;
  --hair: #e6e6e6;
  --paper: #ffffff;
  --soft: #fafafa;
  /* app-screen palette, shared with DESIGN.md */
  --app-bg: #0b0b0c;
  --app-card: #1c1c1e;
  --app-line: #2c2c2e;
  --app-text: #f5f5f7;
  --app-dim: #8e8e93;
  --green: #1fa971;
  --blue: #2f66e8;
  --amber: #b9832a;
  --purple: #7c4fe0;
  /* App-screen red, the app's own --red (see DESIGN.md). Used only on the
     dark mockup screens -- the over-goal calorie ring, the protein dot and
     bar, the .rc-* race screens -- never on --paper; the -on-paper tokens
     below are the ones the contrast test covers. */
  --red: #d1453b;
  /* The form status messages sit on white, not on an app screen. --green
     measures 3.01:1 there (an AA failure for 14px text), and the error red
     was #d1453b at 4.54:1 (fine on --app-* dark grounds, an AA failure here).
     These are the same hues darkened to 4.96:1 and 5.30:1. */
  --green-on-paper: #15804f;
  --red-on-paper: #c13c33;
  /* Long-form body copy on the legal pages. Softer than --ink for a 68ch
     measure without dropping to --muted, and a token rather than a raw hex so
     it is covered by the palette contrast test like every other text colour
     (14.35:1 on --paper). */
  --ink-body: #2a2a2a;

  /* THE FRAME, AND WHAT IT MEANS WHEN THERE ISN'T ONE.

     repcheck-marketing.onrender.com shows this page as a fixed 1080x566
     Instagram landscape canvas; repcheckofficials.onrender.com shows the
     ordinary full-bleed site. Both publish this same directory from the same
     branch, so the difference cannot be a build flag or a media query -- it
     is the rc-canvas class, added by assets/canvas-frame.js from the
     hostname.

     Rather than write every rule twice, the two tokens below are defined
     here as THE WINDOW and redefined under html.rc-canvas as THE FRAME. Every
     use site then reads the same var() and is correct in both shapes, which
     is why there is no `html.rc-canvas` prefix on most of this file.

     100svh and not 100vh or 100dvh: vh ignores the mobile URL bar, so a
     100vh panel hangs off the bottom of the screen behind it, and dvh
     CHANGES as the bar collapses mid-scroll -- which resizes the story while
     you are scrolling through it. svh is the small, stable one. */
  --rc-frame-w: 100vw;
  --rc-frame-h: 100svh;
  /* 1.91:1, and the height is DERIVED from the width rather than typed.
     1080/1.91 is 565.45, so "566" was the ratio rounded to a whole pixel --
     1.908:1, near enough to look right and not what was asked for. Deriving
     it means the ratio is exact at any width, and changing the width alone
     keeps it. */
  --rc-frame-ratio: 1.91;
  /* 1% of the frame, for type and padding that would otherwise be sized in
     vw. vw is the browser WINDOW, which the canvas is not the same size as --
     `6.6vw` on the hero title was 71px in a 1080 window and 95px in a 1440
     one, on a headline laid out in the same 1080px either way, so the hero
     overflowed its 565px frame and the bottom of it was clipped. Off the
     frame it is a constant share of the picture; off the window, above,
     --rc-frame-w is 100vw and this is exactly the 1vw it always meant. */
  --rc-vw: calc(var(--rc-frame-w) / 100);
}

/* THE CANVAS. Only above 720px -- on a phone a 1.91:1 box is 390x204 and
   there is no putting a title, a description and a handset in 204px, so the
   canvas host falls back to the full-bleed shape there too.
   min(), so a window narrower than the canvas shrinks it rather than cropping
   it: without it the width clamped to the viewport while the height stayed
   put, and the ratio went quietly to 1.78:1 on any screen under 1080px, which
   is most laptops once a scrollbar is out.
   vw and NOT %: this value is divided to make a height, and a percentage
   inside a custom property resolves where it is USED -- so 100% became a
   share of the container's HEIGHT and the canvas came out 1080x471, a ratio
   of 2.29. And no max-width alongside it: a cap of 100% measures the content
   box, viewport minus scrollbar, while 100vw counts the scrollbar in, so the
   two disagreed by its width and a 900px window rendered 885 wide against a
   height worked out from 900 -- 1.878:1. The width has to be exactly what the
   height was divided from; body's overflow-x: hidden absorbs the scrollbar. */
@media (min-width: 721px) {
  html.rc-canvas {
    --rc-frame-w: min(1080px, 100vw);
    --rc-frame-h: calc(var(--rc-frame-w) / var(--rc-frame-ratio));
  }
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; background: var(--paper); }
body {
  width: 100%;
  margin: 0 auto;
  overflow-x: hidden;
  font-family: Archivo, "Helvetica Neue", Helvetica, sans-serif;
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
}
/* THE CANVAS IS A WINDOW, AND THE PAGE SCROLLS INSIDE IT.
   The first cut of the frame was a 1080-wide block in a normally-scrolling
   document, which is not the same thing: it was the right size and ratio, but
   on any window taller than 565px you saw straight past its bottom edge to
   whatever the document had there next -- scrolled to the features, 155px of
   page showing under the panel. It was a shape on the page rather than the
   page itself.
   So html holds the viewport and clips, body IS the canvas at exactly the
   frame's size, and the scrolling happens inside body. Everything outside is
   the page's own white. app.js hands document.body to ScrollTrigger as its
   scroller for the same reason -- see the note there.

   Body is flush to the TOP of the window, centred only across it. That is not
   an aesthetic choice: body is the scroller and ScrollTrigger measures a
   trigger's position from the viewport, so any vertical offset on the
   scroller is added to every start it computes. Centred, the canvas sat 117px
   down the window and the hero zoom did not begin until 117px of scrolling
   had already happened -- a dead zone at the top that grew with the reader's
   screen. Flush to the top, that offset is zero. */
/* WHICH ELEMENT SCROLLS MUST NOT DEPEND ON THE WIDTH. These three rules were
   inside the min-width: 721px block with the frame's dimensions, so below it
   the canvas host went back to scrolling the WINDOW while app.js carried on
   scrolling body and handing body to ScrollTrigger -- and body never moved.
   On a phone, repcheck-marketing showed no feature at all: the page scrolled
   and the story sat frozen on its first frame. Out here, body is the scroller
   on that host at every width, and only its SIZE is a frame below. */
html.rc-canvas {
  height: 100%;
  overflow: hidden;
}
html.rc-canvas > body {
  height: 100%;
  overflow-y: auto;
  /* No scrollbar inside the frame: it is a 15px stripe down the right of a
     picture, and the page already says to scroll (.rc-scroll-cue). It also
     keeps body's content box equal to its border box, so the CSS and app.js
     cannot disagree about how big the canvas is. */
  scrollbar-width: none;
}
html.rc-canvas > body::-webkit-scrollbar { width: 0; height: 0; }
@media (min-width: 721px) {
  html.rc-canvas > body {
    width: var(--rc-frame-w);
    height: var(--rc-frame-h);
  }
}
a { color: var(--ink); text-decoration: none; }
a:hover { color: var(--ink); }
::selection { background: var(--ink); color: #fff; }
h1, h2, h3, p, ul { margin: 0; }
ul { padding: 0; list-style: none; }
button { font: inherit; }

/* One explicit focus indicator for everything keyboard-reachable. The page
   relied on the browser default, which is fine on the white sections and
   nearly invisible on the dark ones (.nav-cta, .btn-dark, the phone screens).
   :focus-visible keeps it off mouse clicks. The two-tone ring reads on both
   grounds without needing a per-element rule. */
:where(a, button, input, [tabindex]):focus-visible {
  outline: 3px solid var(--ink);
  outline-offset: 2px;
  /* No border-radius here. It is not an outline property, and because
     :where() zeroes specificity this rule sits at (0,1,0) -- it would square
     off any element whose own radius is declared earlier in the file the
     moment one exists. The ring does not need it. */
}
.nav-cta:focus-visible,
.btn-dark:focus-visible {
  outline-color: var(--paper);
  outline-offset: -6px;
}
/* The handset is the third dark ground. Everything in it is tabindex="-1"
   now -- it is a picture of the app, not a form the page is offering -- but
   that is not the same as unfocusable: clicking into the food log's search
   or amount field still focuses it, and a text field matches :focus-visible
   on a mouse click. An ink ring measures ~1.4:1 against --app-line, i.e.
   invisible, so anything that does get focus in here needs this. Scoped to
   the screen rather than to those two fields so it keeps covering whatever
   app.js renders in there next. */
.phone-screen :focus-visible {
  outline-color: var(--paper);
  outline-offset: 1px;
}
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
.skip-link { position: absolute; left: -9999px; }
.skip-link:focus { left: 12px; top: 12px; background: var(--ink); color: #fff; padding: 10px 14px; z-index: 20; }

.mono, .strip, .kicker, .hint, .footer, .ring-label, .date, .score-label {
  font-family: "JetBrains Mono", ui-monospace, monospace;
}

/* ---------- hero ---------- */
.hero {
  position: relative;
  min-height: var(--rc-frame-h);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 26px 30px 30px;
  border-bottom: 1px solid var(--hair);
}
.dotfield { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.nav { position: relative; display: flex; justify-content: space-between; align-items: center; }
.brand { font-size: 17px; font-weight: 800; letter-spacing: -0.03em; }
.nav-links { display: flex; gap: 26px; align-items: center; font-family: "JetBrains Mono", monospace; font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; }
.nav-links a { display: inline-block; }
.nav-cta { background: var(--ink); color: #fff !important; padding: 9px 15px 8px; }

.hero-mid { position: relative; flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; gap: 36px; padding: 6vh 0; }
.hero-mid h1 { max-width: 15ch; font-size: clamp(40px, calc(var(--rc-vw) * 6.6), 104px); line-height: 1.02; font-weight: 600; letter-spacing: -0.035em; text-wrap: balance; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; }
.btn { display: inline-block; font-size: 15px; font-weight: 600; letter-spacing: -0.01em; padding: 15px 26px; border-radius: 999px; }
.btn-dark { background: var(--ink); color: #fff !important; }
.btn-light { background: #fff; border: 1px solid #dcdcdc; }
.btn-store { display: inline-flex; align-items: center; gap: 9px; }
.btn-store-icon { width: 18px; height: 18px; flex: none; }

/* The hero's breathing room, measured against the CANVAS and not the window.
   The base rule above is 6vh, and vh is the browser window -- which the canvas
   is no longer the same size as. In a 800px-tall window that came to 96px of
   padding inside a 565px frame, so the hero laid out 600px of content in it
   and `overflow: hidden` quietly ate the bottom of .hero-foot. Worse, it grew
   with the window: the taller the reader's screen, the more of the hero was
   cut off. Scaled off --rc-frame-h it is a fixed share of the frame at every
   window size. Only above 721px, where the fixed frame applies at all -- on a
   phone the canvas is the screen and 6vh is already the right answer.
   Placed after the base rule on purpose: a media query adds no specificity. */
@media (min-width: 721px) {
  html.rc-canvas .hero-mid { padding: calc(var(--rc-frame-h) * 0.045) 0; gap: 26px; }
  /* The hero's remaining fixed pixels, on the same leash. Between 721px and
     1080px the frame shrinks with the window -- at 760 it is 760x398 -- but
     the outer padding, the foot copy and the gaps did not, so the hero laid
     out 425px of content in a 398px frame and the foot was clipped again.
     The numbers below are the old constants expressed as a share of the
     frame, so they are unchanged at 1080 and come down with it under that. */
  html.rc-canvas .hero { padding: calc(var(--rc-vw) * 2.4) calc(var(--rc-vw) * 2.8) calc(var(--rc-vw) * 2.8); }
}

.hero-foot { position: relative; display: flex; flex-wrap: wrap; gap: 40px; align-items: flex-end; justify-content: space-between; }

.hero-foot p { max-width: 46ch; font-size: 16px; line-height: 1.55; color: var(--muted); }
/* Second half of the same override, and it has to live down here: the rules
   it beats are the two lines above it, and a media query adds no specificity
   -- up in the block with .hero it lost to them at every width. */
@media (min-width: 721px) {
  html.rc-canvas .hero-foot { gap: calc(var(--rc-vw) * 3.7); }
  html.rc-canvas .hero-foot p { font-size: clamp(13px, calc(var(--rc-vw) * 1.48), 16px); }
}

/* ---------- section chrome ---------- */
/* align-items was baseline, which was right while both halves were a line of
   mono type. The left half is the wordmark now, and a flex item with no text
   baseline of its own aligns by its bottom edge -- that sat the logo a few
   pixels high against the label opposite it. */
.strip { display: flex; justify-content: space-between; align-items: center; padding: 22px 30px; font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--meta); border-bottom: 1px solid var(--hair); }
/* Smaller than the 19px in the page header: this is a label in a hairline
   row, not the masthead, and at 19px it read as a second, competing nav. */
.strip-brand .brand-logo { height: 15px; }
.kicker { font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--meta); }

/* ---------- features + phone ---------- */
.what { border-bottom: 1px solid var(--hair); }
.what-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(330px, 1fr)); }
/* The rows are titles only, so their natural height falls well short of the
   phone beside them. Share the column's height between them rather than
   leave the hairline running down 200px of empty white at the bottom. */
.feature-list { display: flex; flex-direction: column; border-right: 1px solid var(--hair); }
/* Column, not row: the button holds a head and a description line now, and
   the original centred row laid them out side by side. justify-content keeps
   the pair centred in the row's shared height, which is what align-items was
   doing before the second child existed. */
.feature {
  flex: 1 0 auto;
  display: flex; flex-direction: column; align-items: flex-start; justify-content: center;
  gap: 10px;
  width: 100%; text-align: left; appearance: none;
  border: none; border-bottom: 1px solid var(--hair);
  background: #fff; color: var(--ink); cursor: pointer;
  padding: 24px 30px;
  transition: background 0.2s ease, color 0.2s ease;
}
.feature.is-active { background: var(--ink); color: #fff; }
.feature-head { display: flex; align-items: baseline; gap: 16px; }
.idx { font-family: "JetBrains Mono", monospace; font-size: 11px; letter-spacing: 0.14em; opacity: 0.5; }
.feature-title { font-size: clamp(21px, calc(var(--rc-vw) * 2.4), 32px); font-weight: 700; letter-spacing: -0.03em; line-height: 1.05; }
/* Measured, not full-bleed: the column is up to 1fr of a wide grid, and a
   description running its whole width reads as a paragraph of body copy
   rather than a caption under a name. */
.feature-desc { max-width: 52ch; font-size: 14.5px; line-height: 1.55; color: var(--muted); }
/* The active row in the list is filled with ink, so --muted on it would be
   grey on near-black. This is the only place the pair inverts -- the scroll
   story never fills the row (see .rc-story-on .feature.is-active), and its
   own override lives with the rest of that block. */
.feature.is-active .feature-desc { color: rgba(255, 255, 255, 0.78); }
.hint { padding: 24px 30px; font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--meta); }
/* Sits directly under .hint, so it drops the big top padding and keeps the
   pair reading as one block. */
.hint-demo { padding-top: 0; }

.phone-stage { display: flex; align-items: center; justify-content: center; gap: 30px; padding: 48px 30px; background: var(--soft); }
.phone { width: 322px; max-width: 100%; border-radius: 46px; padding: 11px; background: #161616; box-shadow: 0 30px 70px rgba(0,0,0,.22); }
.phone-screen { border-radius: 36px; background: var(--app-bg); color: var(--app-text); overflow: hidden; height: 640px; display: flex; flex-direction: column; }
.status { display: flex; justify-content: space-between; padding: 14px 20px 8px; font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--app-dim); }

.screen { display: none; flex: 1; flex-direction: column; gap: 12px; padding: 14px 18px; overflow: hidden; }
.screen.is-active { display: flex; }
.screen--video { padding: 0; gap: 0; }

.scr-head { display: flex; justify-content: space-between; align-items: center; }
.scr-head b { font-size: 21px; font-weight: 800; letter-spacing: -0.025em; }
.avatar { width: 30px; height: 30px; border-radius: 50%; background: #14213f; color: #9dbaff; display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 700; }

.card { padding: 16px; border-radius: 20px; background: var(--app-card); display: flex; flex-direction: column; gap: 12px; }
.row { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.card-title { font-size: 12.5px; font-weight: 700; white-space: nowrap; }
.pills { display: flex; gap: 5px; }
.pill { padding: 5px 9px; border-radius: 999px; background: var(--app-line); font-size: 11px; font-weight: 600; white-space: nowrap; }
.pill-blue { background: var(--blue); font-weight: 700; }
.pill-amber { background: var(--amber); font-weight: 700; }
.pill-purple { background: #2a1f45; color: #b79bf5; font-weight: 700; align-self: flex-start; }
.sub { font-size: 11.5px; color: var(--app-dim); }
.date { font-size: 9.5px; letter-spacing: .13em; color: var(--app-dim); }
.big { font-size: 26px; font-weight: 800; letter-spacing: -0.03em; }
.big em { font-size: 13px; font-style: normal; font-weight: 600; color: var(--app-dim); }
.section-label { font-size: 13px; font-weight: 800; }
.cta-blue { padding: 13px; border-radius: 15px; background: var(--blue); text-align: center; font-size: 13.5px; font-weight: 700; display: flex; align-items: center; justify-content: center; gap: 8px; }
.banner { padding: 13px 15px; border-radius: 16px; background: var(--amber); font-size: 12.5px; font-weight: 700; display: flex; justify-content: space-between; align-items: center; }
.centered { text-align: center; }
.link { font-size: 12px; color: #9dbaff; }
.pulse { animation: rc-pulse 2.6s ease-in-out infinite; }

/* analysis screen */
.an-video { position: relative; height: 232px; background: repeating-linear-gradient(135deg,#17171a 0 10px,#141416 10px 20px); display: flex; align-items: center; justify-content: center; }
.an-back { position: absolute; top: 12px; left: 14px; width: 30px; height: 30px; border-radius: 50%; background: rgba(28,28,30,.85); display: flex; align-items: center; justify-content: center; font-size: 14px; }
.an-title { position: absolute; top: 18px; left: 0; right: 0; text-align: center; font-size: 14px; font-weight: 700; }
.an-scan { position: absolute; left: 0; right: 0; height: 2px; background: var(--green); opacity: .55; animation: rc-scan 2.6s ease-in-out infinite; }
.an-play { width: 54px; height: 54px; border-radius: 50%; background: rgba(255,255,255,.94); color: var(--app-bg); display: flex; align-items: center; justify-content: center; }
.an-meta { position: absolute; bottom: 10px; left: 0; right: 0; text-align: center; font-family: "JetBrains Mono", monospace; font-size: 9px; letter-spacing: .12em; color: var(--app-dim); }
.an-body { padding: 18px 20px; display: flex; flex-direction: column; gap: 12px; }
.grab { width: 38px; height: 4px; border-radius: 999px; background: var(--app-line); align-self: center; }
.score { display: block; font-size: 54px; font-weight: 800; letter-spacing: -0.04em; color: var(--green); line-height: 1; }
.score-label { display: block; margin-top: 4px; font-size: 9.5px; letter-spacing: .14em; color: var(--app-dim); }
.verdict { font-size: 14px; font-weight: 700; }
.tip { padding: 11px 13px; border-radius: 12px; background: #14213f; color: #9dbaff; font-size: 12.5px; line-height: 1.45; }
.ask { padding: 11px 14px; border-radius: 999px; background: var(--app-card); color: var(--app-dim); font-size: 12.5px; display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.ask span { display: flex; gap: 8px; align-items: center; }
.ask i { color: #b79bf5; font-style: normal; }
.ask b { width: 26px; height: 26px; flex: none; border-radius: 50%; background: var(--purple); color: #fff; display: flex; align-items: center; justify-content: center; font-size: 13px; }

/* nutrition screen */
.ring { position: relative; width: 136px; height: 136px; align-self: center; display: flex; flex-direction: column; align-items: center; justify-content: center; }
.ring-track, .ring-arc { position: absolute; inset: 0; border-radius: 50%; }
.ring-track { border: 9px solid var(--app-line); }
.ring-arc { border: 9px solid transparent; border-top-color: var(--green); animation: rc-spin 9s linear infinite; }
.ring b { position: relative; font-size: 30px; font-weight: 800; letter-spacing: -0.03em; }
.ring-label { position: relative; font-size: 8.5px; letter-spacing: .16em; color: var(--app-dim); }
.ring-sub { position: relative; font-size: 11px; color: #6b6d78; }
.macros { display: grid; grid-template-columns: repeat(3, 1fr); gap: 7px; }
.mac { padding: 9px 10px; border-radius: 13px; background: var(--app-line); }
.mac span { font-family: "JetBrains Mono", monospace; font-size: 8.5px; letter-spacing: .1em; }
.mac b { display: block; margin: 4px 0 6px; font-size: 13px; }
.mac i { display: block; height: 3px; border-radius: 2px; background: #3a3a3c; }

/* week strips */
.week { display: flex; justify-content: space-between; align-items: center; }
.week--labels { align-items: flex-end; }
.day { display: flex; flex-direction: column; align-items: center; gap: 5px; font-size: 9.5px; color: var(--app-dim); }
.dot { width: 26px; height: 26px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 700; background: var(--app-line); color: var(--app-text); border: 1.5px solid transparent; }
.dot.is-done { background: var(--green); color: var(--app-bg); }
.dot.is-today { border-color: var(--blue); }
.arrow { color: var(--app-dim); font-size: 12px; }

/* workout empty state */
.empty { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 14px 0 6px; text-align: center; }
.empty b { font-size: 13.5px; }
.blob { width: 52px; height: 52px; border-radius: 50%; background: #14213f; }

/* hyrox screen */
.clock { font-size: 42px; font-weight: 800; letter-spacing: -0.04em; line-height: 1; font-variant-numeric: tabular-nums; }
/* minmax(0, 1fr), not bare 1fr: a bare 1fr track's minimum is auto (its
   content's width), so an unbreakable label like "Burpees" or "SkiErg"
   forces that whole column wider than its fair share -- which pushed the
   4th column past the card's right edge, since nothing clips overflow at
   the card level (only the phone frame does, further out). minmax(0, 1fr)
   caps every column at its fair share regardless of content. */
.stchips { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 7px; }
/* Top-aligned, not centred: half these labels wrap to two lines ("Sled
   Push", "Wall Balls") and centring them pushed their number up, so no two
   chips in a row put their number -- or the first line of their name -- on
   the same line as each other. */
.stchip { aspect-ratio: 1; border-radius: 13px; background: var(--app-line); display: flex; flex-direction: column; align-items: center; justify-content: flex-start; gap: 4px; padding: 12px 3px 8px; }
.stchip span { font-family: "JetBrains Mono", monospace; font-size: 9px; color: var(--app-dim); }
.stchip b { font-size: 10.5px; text-align: center; line-height: 1.15; padding: 0 4px; }

/* friends + challenges screen (feature 06) */
.code-row { display: flex; gap: 14px; align-items: center; }
.qr { width: 64px; height: 64px; border-radius: 10px; background: repeating-conic-gradient(#f5f5f7 0 25%, #0b0b0c 0 50%) 0 0/16px 16px; }
.code { display: block; font-family: "JetBrains Mono", monospace; font-size: 16px; letter-spacing: 0.04em; margin-bottom: 9px; }
.add-row { display: flex; gap: 7px; }
.fake-input { flex: 1; padding: 10px 12px; border-radius: 12px; background: var(--app-line); font-size: 11.5px; color: #6b6d78; }
.scan { padding: 10px; border-radius: 12px; background: var(--app-line); font-size: 11.5px; text-align: center; }
.friend { display: flex; align-items: center; gap: 10px; font-size: 12.5px; font-weight: 600; }
.friend em { margin-left: auto; font-style: normal; font-weight: 400; color: var(--app-dim); }
.fav { width: 26px; height: 26px; border-radius: 50%; background: var(--app-line); color: var(--app-dim); display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 700; }

/* The two panes are stacked in the same box and cross-faded, so the screen
   keeps the height of one of them. inset matches .screen's own padding,
   which the panes take over now that they, not the cards, are its children. */
.rc-ch-screen { position: relative; }
/* inset is .screen's own padding, which the panes take over now that they,
   not the cards, are its children -- 12px rather than 14px at the top and
   bottom because both panes are within a few pixels of the height they have
   and .screen is overflow:hidden. */
.rc-ch-pane {
  position: absolute; inset: 12px 18px;
  display: flex; flex-direction: column; gap: 12px;
  animation: rc-ch-cycle 11s ease-in-out infinite;
}
/* Half a cycle apart: one pane holds while the other is faded out.
   opacity 0 is the pane's own value, not just where its keyframes start, and
   it has to be: an animation-delay does NOT fill backwards, so through the
   first 5.5 seconds -- which is exactly when a reader first reaches feature
   06 -- this pane had no animated value at all and sat at the CSS default of
   1, drawing the friends list on top of the challenge screen. The declaration
   below is what it holds during the delay. It also means the reduced-motion
   block only has to stop the animation for the right pane to be left. */
.rc-ch-pane--friends { opacity: 0; animation-delay: 5.5s; }
/* The friends pane laid out 8px taller than the box the panes share, and
   .screen is overflow:hidden, so the foot of the friends list was being cut.
   Two paddings, not a smaller type size: the pane has to read as the same
   screen the site shipped before, and 11px of leading is not visible where a
   dropped line would be. Re-measure both if a row is added to it. */
.rc-ch-pane--friends { gap: 10px; }
.rc-ch-pane--friends .scan { padding: 7px; }
.rc-ch-pane--friends .code { margin-bottom: 4px; }
/* Out first, in after, the same hand-off the feature titles use above -- the
   pane fades UP from 0 at the start of its own turn rather than cutting in.
   Without the 0% the delayed pane snapped on at full strength the instant the
   other finished fading, which read as a glitch rather than a change. */
@keyframes rc-ch-cycle {
  0% { opacity: 0; }
  5%, 45% { opacity: 1; }
  50%, 100% { opacity: 0; }
}

/* The hero: today's exercise, its streak chip and the record button. The
   icon is drawn in currentColor (it is the app's own hero SVG), so it takes
   the card's text colour rather than carrying a fill of its own. */
/* Not var(--amber): that tone is meant to be a BACKGROUND on these screens
   (see .pill-amber), and as 11px text on the pill's own --app-line ground it
   measures 4.14:1 -- under AA. This is the same hue lifted to 6.6:1 there. */
.rc-ch-streak { display: inline-flex; align-items: center; gap: 5px; color: #f0a63a; }
.rc-ch-ex { display: flex; align-items: center; gap: 14px; }
.rc-ch-icon { display: flex; flex: none; color: var(--app-text); }
.rc-ch-icon svg { display: block; }
.rc-ch-title { font-size: 24px; font-weight: 800; letter-spacing: -0.02em; }

/* The two leaderboard scopes. A pill pair, not buttons: nothing in the
   handset is interactive. */
.rc-ch-tabs { display: flex; gap: 4px; padding: 3px; border-radius: 999px; background: var(--app-line); }
.rc-ch-tab { padding: 4px 10px; border-radius: 999px; font-size: 10.5px; font-weight: 700; color: var(--app-dim); }
.rc-ch-tab.is-on { background: var(--app-card); color: var(--app-text); }

.rc-ch-row { display: flex; align-items: center; gap: 9px; font-size: 12.5px; font-weight: 600; }
/* Your own row is pinned above the board rather than listed inside it, which
   is what the real page does -- so it is the one row that is tinted. */
.rc-ch-row.is-me { padding: 7px 9px; margin: -7px -9px; border-radius: 12px; background: var(--app-line); }
.rc-ch-rank { width: 22px; flex: none; font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--app-dim); }
.rc-ch-who { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rc-ch-reps { margin-left: auto; font-style: normal; font-weight: 400; color: var(--app-dim); }

/* app tab bar */
.tabbar { margin-top: auto; display: flex; align-items: center; gap: 10px; padding: 10px 16px 18px; }
.tabpill { flex: 1; display: flex; justify-content: space-between; align-items: center; padding: 7px 10px; border-radius: 999px; background: var(--app-card); }
.tab { width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: var(--app-dim); }
.tab svg { width: 19px; height: 19px; }
.tab.is-active { background: var(--app-line); color: var(--app-text); }
.fab { width: 46px; height: 46px; flex: none; border-radius: 50%; background: var(--blue); color: #fff; display: flex; align-items: center; justify-content: center; font-size: 24px; line-height: 1; }

/* ---------- stats ---------- */
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); border-bottom: 1px solid var(--hair); }
.stats > div { padding: 44px 30px; border-right: 1px solid var(--hair); }
.stats b { display: block; font-size: clamp(44px, calc(var(--rc-vw) * 5), 76px); font-weight: 900; letter-spacing: -0.05em; line-height: 1; font-variant-numeric: tabular-nums; }
.stats span { display: block; margin-top: 10px; font-family: "JetBrains Mono", monospace; font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--meta); }

/* ---------- waitlist ---------- */
.cta { padding: clamp(56px, calc(var(--rc-vw) * 9), 140px) 30px; display: flex; flex-direction: column; align-items: flex-start; gap: 28px; }
.cta h2 { font-size: clamp(36px, calc(var(--rc-vw) * 7), 116px); font-weight: 900; letter-spacing: -0.05em; line-height: 0.94; max-width: 14ch; }
.waitlist-form { display: flex; flex-wrap: wrap; gap: 14px; align-items: center; width: 100%; max-width: 620px; border-bottom: 2px solid var(--ink); padding-bottom: 10px; }
.waitlist-form input { flex: 1 1 240px; border: none; background: transparent; font-family: Archivo, sans-serif; font-size: clamp(20px, calc(var(--rc-vw) * 2.6), 32px); font-weight: 600; letter-spacing: -0.02em; color: var(--ink); padding: 6px 0; }
/* The email field had `outline: none` and no replacement, so a keyboard
   user got NO indication of where they were -- the underline belongs to
   .waitlist-form, not to the input, so nothing moved on focus. WCAG 2.4.7.
   Now the whole field lights up (thicker rule) AND the input carries a
   real ring. :focus-visible, so it shows for keyboard and not on mouse. */
.waitlist-form:focus-within { border-bottom-width: 3px; padding-bottom: 9px; }
/* Only the offset differs from the global ring -- the width and colour come
   from the one place that defines them. */
.waitlist-form input:focus-visible { outline-offset: 4px; }
.waitlist-form input[aria-invalid="true"] { color: var(--red-on-paper); }
.waitlist-form button { border: none; background: var(--ink); color: #fff; font-family: "JetBrains Mono", monospace; font-size: 12px; letter-spacing: 0.14em; text-transform: uppercase; padding: 16px 24px; cursor: pointer; }
.waitlist-form button[disabled] { opacity: 0.6; cursor: default; }
.form-note { font-size: 14px; color: var(--meta); max-width: 52ch; }
/* The status line starts empty now, and an empty <p> is zero pixels tall --
   so the consent notice under it would sit one line higher until someone
   submitted, then jump as the result arrived. A non-breaking space is what
   holds the space: it makes a real line box, with the same font metrics as
   the message that will replace it, so the two are the same height by
   construction. A min-height cannot promise that -- measured, 1.4em came out
   at 19.6px against a real message line of 15px, and the block jumped 5px
   the other way. */
.form-note:empty::before { content: "\00a0"; }
.form-note.is-error { color: var(--red-on-paper); }
.form-note.is-ok { color: var(--green-on-paper); }

/* ---------- footer ---------- */
.footer { border-top: 1px solid var(--hair); padding: 26px 30px; display: flex; flex-wrap: wrap; gap: 18px; justify-content: space-between; align-items: center; font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--meta); }
.footer .brand { font-family: Archivo, sans-serif; font-size: 15px; text-transform: none; letter-spacing: -0.03em; color: var(--ink); }

/* Legal links and the operator line. Underlined rather than colour-only, so
   they are still identifiable as links without relying on colour (WCAG 1.4.1),
   and --ink rather than --meta because these are the links someone goes
   looking for. */
/* Two halves, each allowed to wrap internally, so the footer never ends up
   with a single orphan pushed to the far edge by space-between. */
.footer-group { display: flex; flex-wrap: wrap; align-items: baseline; gap: 12px 22px; }
.footer-legal { display: flex; gap: 16px; flex-wrap: wrap; }
.footer-legal a { color: var(--ink); text-decoration: underline; }
.footer-operator { text-transform: none; letter-spacing: 0.04em; }
.footer-operator a { color: var(--ink); text-decoration: underline; }

/* The consent line under the waitlist field. 14px and --muted (6.69:1), not
   the 11px --meta the rest of the footer uses: it carries terms someone is
   agreeing to, so it has to be comfortably readable rather than decorative. */
.form-consent {
  font-size: 14px; line-height: 1.55; color: var(--muted); max-width: 62ch; margin: 0;
}
.form-consent a { color: var(--ink); text-decoration: underline; }

@keyframes rc-scan { 0%, 100% { transform: translateY(-78px); } 50% { transform: translateY(78px); } }
@keyframes rc-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .62; } }
@keyframes rc-spin { to { transform: rotate(360deg); } }

@media (max-width: 720px) {
  .hero, .strip, .feature, .hint, .stats > div, .cta, .footer { padding-left: 20px; padding-right: 20px; }
  .nav-links { gap: 14px; font-size: 10px; }
  .feature-list { border-right: none; }
}

@media (prefers-reduced-motion: reduce) {
  .pulse, .an-scan, .ring-arc { animation: none; }
  * { transition-duration: 0.01ms !important; }
}

/* ---------- analyze showcase (feature 01's phone screen) ----------
   Screen 0 of the phone switcher shows the real screen recording instead
   of a mocked score. .screen[data-screen="0"] already has padding:0/gap:0
   from the base design, so the video fills the same slot the mockup did. */
.screen--video { position: relative; background: #000; }
.screen--video video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ---------- workout log walkthrough (feature 04's phone screen) ----------
   The screen plays out the one thing the feature is about: an empty day, the
   tap on "+ Log an exercise", then the weight and the reps going in. Built
   from the app-screen components already defined above (.card, .cta-blue,
   .empty, .sub, .date) -- these rules only add the set editor, which had no
   equivalent yet, and the reveal that steps between the two states.

   data-wl on the screen is the beat: 0 empty, 1 tapped, picker sheet up,
   picked the row pressed, 2 exercise added, 3 weight and reps filled in.
   Everything is in the markup from the start and revealed here, so no JS
   writes HTML beyond the weight/reps numbers themselves. */
.wl-entry, .wl-set, .wl-addset, .wl-count { display: none; }
.wl-log { transition: transform .18s cubic-bezier(.2,.8,.2,1), filter .18s ease; }
.screen[data-wl] { position: relative; }

/* 1 -- the tap: the button presses in, nothing has landed yet. */
.screen[data-wl="1"] .wl-log { transform: scale(.97); filter: brightness(1.25); }

/* picker, picked -- the exercise sheet slides up over the empty state and a
   row gets pressed before the sheet goes back down into beat 2. */
.wl-picker {
  position: absolute; left: 14px; right: 14px; bottom: 14px;
  display: flex; flex-direction: column; gap: 10px;
  padding: 12px 14px 16px; border-radius: 20px;
  background: var(--app-card); box-shadow: 0 -14px 34px rgba(0,0,0,.4);
  transform: translateY(115%);
  transition: transform .32s cubic-bezier(.2,.8,.2,1);
}
.screen[data-wl="picker"] .wl-picker,
.screen[data-wl="picked"] .wl-picker { transform: translateY(0); }
.wl-pick-row {
  display: flex; align-items: center; gap: 10px; padding: 8px;
  border-radius: 12px;
  transition: transform .16s ease, filter .16s ease, background .16s ease;
}
.wl-pick-row b { font-size: 12.5px; font-weight: 700; }
/* app-card, not the shared .wl-icon app-line: that's also what the pressed
   row's own background turns, and same-on-same made the icon vanish. */
.wl-pick-row .wl-icon { background: var(--app-card); }
.screen[data-wl="picked"] .wl-pick-row.is-target {
  transform: scale(.97); filter: brightness(1.3); background: var(--app-line);
}

/* 2, 3 -- the empty state gives way to the logged exercise. */
.screen[data-wl="2"] .wl-empty,
.screen[data-wl="3"] .wl-empty { display: none; }
.screen[data-wl="2"] .wl-entry,
.screen[data-wl="3"] .wl-entry,
.screen[data-wl="2"] .wl-set,
.screen[data-wl="3"] .wl-set,
.screen[data-wl="2"] .wl-addset,
.screen[data-wl="3"] .wl-addset { display: flex; }
.screen[data-wl="2"] .wl-count,
.screen[data-wl="3"] .wl-count { display: inline; }
.screen[data-wl="2"] .wl-entry { animation: wl-in .3s cubic-bezier(.2,.8,.2,1) both; }

.wl-entry { flex-direction: column; gap: 10px; }
.wl-entry-id { display: flex; align-items: center; gap: 9px; min-width: 0; }
.wl-entry-id > span { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.wl-entry-sub { font-size: 10px; }
/* The tile used to be an empty rounded square standing in for an icon. It
   carries the real per-exercise illustration now -- the same artwork the app
   shows, out of the icon pack exercise_icons.py maps (Barbell Bicep Curl and
   EZ-Bar Bicep Curl are one drawing in that pack, not two, so they share a
   file here as well). overflow hidden and object-fit contain match the app's
   own .exercise-icon / .ex-icon-img pair. */
.wl-icon {
  width: 30px;
  height: 30px;
  border-radius: 10px;
  background: var(--app-line);
  flex: none;
  overflow: hidden;
}
.wl-icon img { width: 100%; height: 100%; object-fit: contain; display: block; }

.wl-set { flex-direction: column; gap: 8px; padding: 10px; border-radius: 14px; background: var(--app-line); }
.wl-fields { display: flex; align-items: center; gap: 7px; }
.wl-field {
  flex: 1; display: flex; align-items: center; justify-content: space-between; gap: 6px;
  padding: 8px 10px; border-radius: 11px; background: var(--app-card);
}
.wl-field b { font-size: 15px; font-weight: 800; }
.wl-addset {
  justify-content: center; padding: 9px; border-radius: 11px;
  border: 1px dashed var(--app-line); color: #6f9bff;
  font-size: 11.5px; font-weight: 700; text-align: center;
}

@keyframes wl-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .screen[data-wl="2"] .wl-entry { animation: none; }
  .screen[data-wl="1"] .wl-log { transform: none; filter: none; }
}

/* ---------- race walkthrough (feature 05's screen) ----------
   The HYROX feature doesn't show a still -- its screen is the playable race
   simulator, driven from inside the handset itself (start, set up, complete a
   split, read the finish back). It reuses the app-screen components already
   defined above (.card, .row, .pill, .date, .clock, .sub, .stchips,
   .cta-blue), so it is the same phone as the other five features.

   It is a .screen like the rest so the feature switcher owns it; the only
   extra is its own scrolling -- the handset scrolls its screen like the real
   thing, where the mocked-up screens never needed to. */
.screen--race { overflow-y: auto; overflow-x: hidden; scrollbar-width: none; position: relative; }
.screen--race::-webkit-scrollbar { display: none; }

/* Station chips on the HYROX screen are buttons here (tap for how it's done),
   so they need the button reset the mocked-up divs never needed. */
button.stchip { appearance: none; border: none; font: inherit; color: inherit; cursor: pointer; }
button.stchip:hover { background: #34343a; }
.cta-blue { appearance: none; border: none; font: inherit; color: #fff; cursor: pointer; width: 100%; }

/* ----- race setup ----- */
.rc-choices { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; }
.rc-choice {
  appearance: none; border: 1.5px solid var(--app-line); border-radius: 13px;
  background: var(--app-line); color: var(--app-text);
  font: inherit; text-align: left; cursor: pointer; padding: 9px 11px;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.rc-choice b { display: block; font-size: 12.5px; font-weight: 700; }
.rc-choice span { display: block; margin-top: 2px; font-size: 10px; color: var(--app-dim); }
.rc-choice:hover { border-color: var(--blue); }
.rc-choice.is-on { border-color: var(--blue); background: #14213f; }
.rc-choice.is-on span { color: #9dbaff; }

.rc-ag-row { display: flex; align-items: center; gap: 9px; font-size: 11.5px; }
.rc-ag-row.is-runs { padding-bottom: 9px; border-bottom: 1px solid var(--app-line); }
.rc-ag-icon { color: var(--app-dim); display: flex; }
.rc-ag-name { font-weight: 700; }
.rc-ag-meta { margin-left: auto; color: var(--app-dim); font-variant-numeric: tabular-nums; }

.rc-pb { background: #3a2f14; }
.rc-pb-clock { font-size: 30px; color: #f0c06a; }

/* ----- running ----- */
.rc-run-head { display: flex; justify-content: space-between; align-items: flex-end; }
.rc-run-head .date { display: block; margin-top: 5px; }
.rc-run-seg { text-align: right; }
.rc-run-seg b { font-size: 30px; font-weight: 800; letter-spacing: -0.03em; color: #9dbaff; font-variant-numeric: tabular-nums; }
.rc-run-seg i { font-size: 17px; font-style: normal; color: var(--app-dim); }

.rc-now { align-items: center; text-align: center; background: #14213f; }
.rc-now-badge {
  width: 62px; height: 62px; border-radius: 20px; display: flex; align-items: center; justify-content: center;
  background: var(--blue); color: #fff;
}
.rc-now-title { font-size: 21px; font-weight: 800; letter-spacing: -0.03em; }
.rc-now-figure b { display: block; font-size: 28px; font-weight: 800; letter-spacing: -0.03em; line-height: 1; font-variant-numeric: tabular-nums; }
.rc-now-figure span { display: block; margin-top: 4px; font-family: "JetBrains Mono", monospace; font-size: 9px; letter-spacing: .14em; color: #9dbaff; }
.rc-now-figure.is-share b { color: #b79bf5; }
.rc-now-figure.is-share span { color: #b79bf5; }
.rc-now-chips { display: flex; gap: 7px; justify-content: center; }
.rc-now-chip { flex: 1; padding: 8px 6px; border-radius: 12px; background: rgba(255,255,255,.06); }
.rc-now-chip b { display: block; font-size: 14px; font-weight: 800; font-variant-numeric: tabular-nums; }
.rc-now-chip span { display: block; margin-top: 3px; font-family: "JetBrains Mono", monospace; font-size: 8.5px; letter-spacing: .12em; color: var(--app-dim); }
.rc-how {
  appearance: none; border: none; background: transparent; cursor: pointer;
  font-family: "JetBrains Mono", monospace; font-size: 10px; letter-spacing: .14em;
  text-transform: uppercase; color: #9dbaff; padding: 0;
}

.rc-dots { display: flex; gap: 3px; }
.rc-dot { flex: 1; height: 3px; border-radius: 2px; background: var(--app-line); }
.rc-dot.is-done { background: var(--green); }
.rc-dot.is-now { background: var(--blue); }

.rc-complete {
  appearance: none; border: none; cursor: pointer; width: 100%;
  padding: 15px; border-radius: 16px; background: var(--green); color: #04240f;
  font-family: inherit; font-size: 15px; font-weight: 800;
}
.rc-cancel {
  appearance: none; border: none; background: transparent; cursor: pointer;
  font-family: "JetBrains Mono", monospace; font-size: 9.5px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--red); padding: 0;
}
.rc-splits { display: flex; flex-direction: column; }
.rc-split { display: flex; align-items: center; gap: 8px; padding: 8px 0; border-bottom: 1px solid var(--app-line); font-size: 11.5px; }
.rc-split:last-child { border-bottom: none; }
.rc-split-icon { color: var(--app-dim); display: flex; }
.rc-split-name { font-weight: 600; }
.rc-split b { margin-left: auto; font-variant-numeric: tabular-nums; }
.rc-split i { width: 52px; text-align: right; font-style: normal; color: var(--app-dim); font-variant-numeric: tabular-nums; }

/* ----- finish ----- */
.rc-finish { text-align: center; padding: 8px 0 2px; }
.rc-finish-clock { display: block; margin-top: 6px; font-size: 44px; }
.rc-bd-totals { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; }
.rc-bd-total { padding: 10px; border-radius: 13px; text-align: center; }
.rc-bd-total b { display: block; font-size: 17px; font-weight: 800; font-variant-numeric: tabular-nums; }
.rc-bd-total span { display: block; margin-top: 3px; font-family: "JetBrains Mono", monospace; font-size: 8.5px; letter-spacing: .12em; }
.rc-bd-total.is-run { background: #14213f; color: #9dbaff; }
.rc-bd-total.is-station { background: #12321f; color: #6fd8a6; }
.rc-bd-row { display: grid; grid-template-columns: 1fr 58px 40px; gap: 8px; align-items: center; font-size: 11px; }
.rc-bd-row b { text-align: right; font-variant-numeric: tabular-nums; }
.rc-bd-row.is-run .rc-bd-name { color: var(--app-dim); }
.rc-bd-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-weight: 600; }
.rc-bd-track { height: 5px; border-radius: 3px; background: var(--app-line); overflow: hidden; }
.rc-bd-track > span { display: block; height: 100%; background: #4a4a4f; }
.rc-bd-row.is-focus .rc-bd-track > span { background: var(--red); }
.rc-bd-row.is-strong .rc-bd-track > span { background: var(--green); }
.rc-bd-row.is-solid .rc-bd-track > span { background: var(--blue); }

.rc-coach-overall { font-size: 13.5px; font-weight: 700; line-height: 1.4; }
.rc-coach-bullets { display: flex; flex-direction: column; gap: 5px; margin: 0; padding-left: 15px; }
.rc-coach-bullets li { font-size: 11.5px; line-height: 1.45; color: var(--app-dim); }
.rc-tips { display: flex; flex-direction: column; gap: 9px; padding-top: 11px; border-top: 1px solid var(--app-line); }
.rc-tips.is-focus .date { color: #ef7d74; }
.rc-tips.is-strong .date { color: #6fd8a6; }
.rc-tip { display: flex; gap: 9px; }
.rc-tip-icon { color: var(--app-dim); display: flex; padding-top: 1px; }
.rc-tip b { display: block; font-size: 12px; }
.rc-tip span { display: block; margin-top: 2px; font-size: 11.5px; line-height: 1.45; color: var(--app-dim); }

/* ----- "how it's done" sheet, inside the handset ----- */
.rc-sheet { position: absolute; inset: 0; background: rgba(0,0,0,.55); display: flex; align-items: flex-end; }
.rc-sheet-card {
  width: 100%; background: var(--app-card); border-radius: 22px 22px 0 0;
  padding: 12px 16px 18px; display: flex; flex-direction: column; gap: 11px;
}
.rc-sheet-title { display: flex; align-items: center; gap: 8px; font-size: 15px; font-weight: 800; }
.rc-sheet-close {
  appearance: none; border: none; cursor: pointer; width: 26px; height: 26px; border-radius: 50%;
  background: var(--app-line); color: var(--app-dim); font-size: 16px; line-height: 1;
}
.rc-sheet-detail { margin: 0; font-size: 12px; line-height: 1.5; color: var(--app-dim); }

/* ----- Apple Watch companion (concept, labelled as one) ----- */
.watch-rig { width: 186px; flex: none; animation: rc-watch-in .34s ease both; }
@keyframes rc-watch-in { from { opacity: 0; transform: translateX(-10px); } to { opacity: 1; transform: none; } }
.watch {
  position: relative; width: 172px; margin-inline: auto;
  border-radius: 46px; padding: 11px 9px;
  background: linear-gradient(160deg, #3a3c42, #161616 55%);
  box-shadow: 0 24px 48px rgba(0,0,0,.24);
}
/* digital crown + side button */
.watch::before, .watch::after {
  content: ""; position: absolute; right: -3px; width: 4px; border-radius: 3px;
  background: linear-gradient(180deg, #6d6f77, #2c2d33);
}
.watch::before { top: 33%; height: 26px; }
.watch::after { top: 53%; height: 18px; }
.watch-screen {
  background: #000; border-radius: 38px; padding: 14px 12px 12px;
  color: var(--app-text); text-align: center;
  display: flex; flex-direction: column; gap: 3px;
}
.watch-top { display: flex; justify-content: space-between; font-family: "JetBrains Mono", monospace; font-size: 9.5px; color: var(--app-dim); }
.watch-seg { color: #9dbaff; }
.watch-clock { font-size: 29px; font-weight: 800; letter-spacing: -0.03em; line-height: 1.1; color: #30d158; font-variant-numeric: tabular-nums; }
.watch-label { font-family: "JetBrains Mono", monospace; font-size: 8.5px; letter-spacing: .16em; color: var(--app-dim); }
.watch-now { display: flex; align-items: center; justify-content: center; gap: 5px; margin-top: 7px; font-size: 12.5px; font-weight: 800; }
.watch-now svg { color: #9dbaff; }
.watch-meta { font-size: 10.5px; color: var(--app-dim); }
.watch-bar { height: 4px; border-radius: 2px; background: #2c2c2e; margin: 9px 0 3px; overflow: hidden; }
.watch-bar > span { display: block; height: 100%; background: #30d158; }
.watch-btn {
  appearance: none; border: none; cursor: pointer; width: 100%;
  padding: 10px; border-radius: 13px; background: #30d158; color: #04240f;
  font-family: inherit; font-size: 13.5px; font-weight: 800;
}
.watch-next { font-family: "JetBrains Mono", monospace; font-size: 8.5px; letter-spacing: .1em; color: var(--app-dim); margin-top: 6px; }
.watch-screen.is-finish { padding: 30px 12px 26px; gap: 5px; }
.watch-saved { font-size: 10px; font-weight: 700; color: #30d158; margin-top: 8px; }
.watch-note { margin: 14px 0 0; font-size: 12px; line-height: 1.5; color: var(--meta); text-align: center; }
.watch-note b { display: block; margin-bottom: 3px; font-size: 12.5px; color: var(--ink); }

@media (max-width: 1240px) {
  .phone-stage { flex-direction: column; gap: 22px; }
}
/* Side by side, the handset keeps its width when the watch comes in. It
   used to shrink to make room -- 322px to 309 in a 1080 canvas -- so the
   HYROX screen reflowed and the handset changed size on the spot as the watch
   came and went. Scaled as it is in the story, its drawn width is well inside
   the column, so the pair can overhang the stage's padding instead. */
@media (min-width: 1241px) {
  .rc-story-on .phone { flex-shrink: 0; }
}
@media (max-width: 720px) {
  .phone-stage { padding-left: 20px; padding-right: 20px; }
}

/* ---------- legal pages (privacy.html, cookies.html, terms.html) ----------
   These are scoped deliberately narrowly: they cover THIS WEBSITE and the
   waitlist, and point at the in-app policies for the app itself. Two full
   privacy policies that drift apart is worse than one -- do not paste the
   app's policy in here.

   Type is 17px/1.65 on a 68ch measure rather than the 11px mono the marketing
   sections use. A policy nobody can comfortably read is not a policy. */
.legal {
  max-width: 68ch;
  margin: 0 auto;
  padding: 40px 30px 80px;
}
.legal-nav {
  display: flex;
  gap: 18px;
  align-items: baseline;
  justify-content: space-between;
  padding: 26px 30px;
  border-bottom: 1px solid var(--hair);
  flex-wrap: wrap;
}
.legal-nav a { font-family: "JetBrains Mono", monospace; font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink); text-decoration: underline; }
.legal h1 { font-size: clamp(30px, calc(var(--rc-vw) * 5), 46px); font-weight: 800; letter-spacing: -0.03em; line-height: 1.05; margin: 0 0 10px; }
.legal h2 { font-size: 19px; font-weight: 700; letter-spacing: -0.01em; margin: 38px 0 10px; }
.legal p, .legal li { font-size: 17px; line-height: 1.65; color: var(--ink-body); }
.legal ul { list-style: disc; padding-left: 22px; margin: 10px 0 0; }
.legal li { margin-bottom: 9px; }
.legal a { color: var(--ink); text-decoration: underline; }
.legal strong { font-weight: 700; color: var(--ink); }
.legal .legal-updated {
  font-family: "JetBrains Mono", monospace;
  font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--meta); margin: 0 0 30px;
}
/* A callout for the "this page covers the site, the app is over there" note,
   which is the one thing a reader must not miss. */
.legal-scope {
  border-left: 3px solid var(--ink);
  padding: 14px 18px;
  background: var(--soft);
  margin: 0 0 30px;
}
.legal-scope p { margin: 0; font-size: 16px; }

/* ---------- nutrition log (feature 02's phone screen) ----------
   The one screen on this page you can actually use: search the food
   library, set an amount, add it, and the day's ring, macro bars and entry
   list all move. Built from the app-screen components already defined above
   (.card, .row, .macros, .cta-blue, .empty, .grab, .sub) -- the rules here
   only add what had no equivalent yet: the calorie ring, the macro-split
   donut, the amount editor, and the two sheets that slide over the screen.

   The palette and the geometry are the app's own: ring r=52 stroke 10,
   donut r=46 stroke 12, protein red / fat amber / carbs green, matching
   templates/nutrition.html so the demo and the product read as one thing. */
.screen--nl { position: relative; }

/* Sized down from the app's 168px ring: this handset is 322px wide and the
   day's entry list has to stay visible underneath it, not scroll out of
   sight the moment a second food goes in. */
.nl-ring { position: relative; width: 104px; height: 104px; align-self: center; }
.nl-ring-svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.nl-ring-track { fill: none; stroke: var(--app-line); stroke-width: 10; }
.nl-ring-fill {
  fill: none; stroke: var(--blue); stroke-width: 10; stroke-linecap: round;
  transition: stroke-dasharray .35s cubic-bezier(.2,.8,.2,1);
}
.nl-ring-fill.is-over { stroke: var(--red); }
.nl-ring-center {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.nl-ring-center b { font-size: 22px; font-weight: 800; letter-spacing: -0.03em; line-height: 1.05; font-variant-numeric: tabular-nums; }
.nl-ring-center .ring-label { margin-top: 3px; }
.nl-ring-sub { margin-top: 4px; font-size: 10px; color: var(--app-dim); font-variant-numeric: tabular-nums; }

/* The nutrition card carries the most in the least room, so it runs tighter
   than the shared .card: less padding, smaller macro chips. */
.screen--nl > .card { padding: 13px 14px; gap: 10px; }
.screen--nl .mac { padding: 7px 8px; }
.screen--nl .mac b { margin: 3px 0 5px; font-size: 11.5px; white-space: nowrap; }

/* The macro row reuses .mac; the <em> is the filled part of its bar. Scoped
   to .screen--nl like the two rules just above -- .mac is a shared
   app-screen component, and the next screen to reuse .macros should not
   silently inherit this one's bar-fill and number formatting. */
.screen--nl .mac i { overflow: hidden; }
.screen--nl .mac i em { display: block; width: 0; height: 100%; border-radius: 2px; transition: width .35s cubic-bezier(.2,.8,.2,1); }
.screen--nl .mac b { font-variant-numeric: tabular-nums; }
.nl-fill-p { background: var(--red); }
.nl-fill-f { background: var(--amber); }
.nl-fill-c { background: var(--green); }

/* The day's list is the only part that scrolls: the ring card and the log
   button stay put, so "+ Log a food" is always one tap away. */
.nl-day { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 8px; }
.nl-entries { flex: 1; min-height: 0; overflow-y: auto; scrollbar-width: none; display: flex; flex-direction: column; gap: 7px; }
.nl-entries::-webkit-scrollbar { display: none; }
.nl-entry {
  display: flex; align-items: center; gap: 9px;
  padding: 9px 11px; border-radius: 14px; background: var(--app-card);
  animation: nl-in .28s cubic-bezier(.2,.8,.2,1) both;
}
.nl-entry-id { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.nl-entry-id b { font-size: 12px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nl-entry-id .sub { font-size: 10px; font-variant-numeric: tabular-nums; }
.nl-entry-kcal { font-size: 14px; font-weight: 800; font-variant-numeric: tabular-nums; }
@keyframes nl-in { from { opacity: 0; transform: translateY(7px); } to { opacity: 1; transform: none; } }

.nl-x {
  width: 24px; height: 24px; flex: none; padding: 0; border: none; cursor: pointer;
  border-radius: 50%; background: var(--app-line); color: var(--app-dim);
  font-size: 15px; line-height: 1; display: flex; align-items: center; justify-content: center;
}
.nl-x:hover { color: var(--app-text); }

/* The quiet "it is in" moment after Add to log, same as the app's. Sits
   over the list rather than pushing it around. */
.nl-toast {
  position: absolute; left: 18px; right: 18px; bottom: 14px; z-index: 3;
  padding: 10px 13px; border-radius: 13px; background: var(--green); color: #fff;
  font-size: 12px; font-weight: 700; text-align: center;
  opacity: 0; transform: translateY(8px); pointer-events: none;
  transition: opacity .2s ease, transform .2s ease;
}
.nl-toast.is-on { opacity: 1; transform: none; }

/* ---- the two sheets ----
   Closed, they are visibility:hidden, not merely translated away: an
   off-screen sheet with visible content still takes tab focus and still
   reads out to a screen reader. The visibility change is delayed to the end
   of the slide on the way out and instant on the way in, so the movement
   stays visible in both directions. */
.nl-sheet {
  position: absolute; left: 14px; right: 14px; bottom: 14px; z-index: 4;
  display: flex; flex-direction: column; gap: 11px;
  padding: 11px 14px 15px; border-radius: 20px;
  background: var(--app-card); box-shadow: 0 -14px 34px rgba(0,0,0,.45);
  transform: translateY(112%); visibility: hidden;
  transition: transform .3s cubic-bezier(.2,.8,.2,1), visibility 0s linear .3s;
}
.nl-sheet.is-open { transform: translateY(0); visibility: visible; transition: transform .3s cubic-bezier(.2,.8,.2,1), visibility 0s; }
.nl-sheet-head { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.nl-sheet-head .card-title { font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; }

.nl-input {
  width: 100%; padding: 10px 12px; border-radius: 12px;
  border: 1px solid var(--app-line); background: var(--app-bg); color: var(--app-text);
  font-family: inherit; font-size: 12.5px;
}
.nl-input::placeholder { color: #6b6d78; }
/* Two selectors, two different jobs, same look.
   :focus is a visitor who clicked into the field -- the ring itself comes
   from the global :focus-visible rule (and the .phone-screen override that
   makes it visible on this dark ground); the border change here is an extra
   affordance on top of it, never a replacement.
   .is-faux-focus is the walkthrough, which draws the step WITHOUT focusing
   anything: these are mock controls in a picture of the app, and a loop that
   focused them moved the reader's focus for them every nine seconds. See
   nlFakeFocus in app.js. No ring on this one, deliberately -- a focus ring
   on an element that is not focused is a lie to anyone reading it. */
.nl-input:focus, .nl-input.is-faux-focus { border-color: var(--blue); }

.nl-results { max-height: 226px; overflow-y: auto; scrollbar-width: none; display: flex; flex-direction: column; gap: 4px; }
.nl-results::-webkit-scrollbar { display: none; }
.nl-result {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 8px 9px; border: none; border-radius: 12px; background: transparent;
  color: inherit; text-align: left; cursor: pointer;
  transition: background .14s ease;
}
.nl-result:hover, .nl-result:focus-visible { background: var(--app-line); }
.nl-result-id { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.nl-result-id b { font-size: 12.5px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nl-result-id .sub { font-size: 10px; font-variant-numeric: tabular-nums; }
.nl-result-id em { font-style: normal; color: #6b6d78; }
.nl-plus { width: 22px; height: 22px; flex: none; border-radius: 50%; background: var(--blue); color: #fff; font-size: 14px; line-height: 1; display: flex; align-items: center; justify-content: center; }
.nl-none { padding: 16px 4px; text-align: center; font-size: 11.5px; color: var(--app-dim); }

.nl-donut-row { display: flex; align-items: center; gap: 14px; }
.nl-donut-wrap { position: relative; width: 96px; height: 96px; flex: none; }
.nl-donut { width: 100%; height: 100%; }
.nl-donut-track { fill: none; stroke: var(--app-bg); stroke-width: 12; }
.nl-donut-seg { fill: none; stroke-width: 12; stroke-linecap: round; transition: stroke-dasharray .25s ease, stroke-dashoffset .25s ease; }
.nl-seg-p { stroke: var(--red); }
.nl-seg-f { stroke: var(--amber); }
.nl-seg-c { stroke: var(--green); }
.nl-donut-center { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; }
.nl-donut-center b { font-size: 20px; font-weight: 800; color: var(--blue); line-height: 1.1; font-variant-numeric: tabular-nums; }
.nl-donut-label { font-size: 9.5px; font-weight: 600; color: var(--app-dim); margin-top: 1px; }
.nl-legend { flex: 1; display: flex; flex-direction: column; gap: 7px; min-width: 0; }
.nl-legend-row { display: flex; align-items: center; gap: 7px; font-size: 11.5px; }
.nl-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; }
.nl-legend-row b { min-width: 30px; font-weight: 800; font-variant-numeric: tabular-nums; }
.nl-legend-row .sub { flex: 1; }
.nl-legend-row em { font-style: normal; font-weight: 600; color: var(--app-dim); font-variant-numeric: tabular-nums; }

.nl-amount { display: flex; flex-direction: column; gap: 9px; padding: 11px; border-radius: 15px; background: var(--app-line); }
/* Tapping Add on a blank/zero amount used to do nothing visible at all --
   nudge the card that needs fixing, the same shake shape static/tour.css
   uses for a blocked action (this page has no shared stylesheet with the
   app, so it gets its own copy rather than a cross-file dependency). */
.nl-amount.is-nudging { animation: nl-shake 0.36s cubic-bezier(0.36, 0.07, 0.19, 0.97); }
@keyframes nl-shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-7px); }
  40% { transform: translateX(6px); }
  60% { transform: translateX(-4px); }
  80% { transform: translateX(2px); }
}
.nl-amount-row { display: flex; align-items: center; gap: 8px; }
#nl-amount {
  width: 74px; padding: 8px 10px; border-radius: 10px;
  border: 1px solid #3a3a3c; background: var(--app-bg); color: var(--app-text);
  font-family: inherit; font-size: 14px; font-weight: 700; font-variant-numeric: tabular-nums;
}
#nl-amount:focus, #nl-amount.is-faux-focus { border-color: var(--blue); }
.nl-unit-seg { flex: 1; display: flex; gap: 3px; padding: 3px; border-radius: 999px; background: var(--app-bg); }
.nl-unit-seg button {
  flex: 1; padding: 6px 4px; border: none; border-radius: 999px;
  background: transparent; color: var(--app-dim); cursor: pointer;
  font-size: 11px; font-weight: 700;
  transition: background .16s ease, color .16s ease;
}
.nl-unit-seg button.is-active { background: var(--blue); color: #fff; }

/* 16px on the two real inputs, the same trick templates/nutrition.html
   uses: iOS Safari auto-zooms the whole page when a focused input is under
   16px, which would yank a visitor out of the section mid-search. */
@media (max-width: 720px) {
  .nl-input, #nl-amount { font-size: 16px; }
}

@media (prefers-reduced-motion: reduce) {
  .nl-entry, .nl-amount.is-nudging { animation: none; }
}

/* ---------- scroll story: section 01, driven by scroll position ---------- */
/* Every rule in this block is gated on .rc-story-on, which app.js puts on
   <html> only once it has confirmed IntersectionObserver exists and has built
   the scroll markers. Without JS -- or on a browser without IO -- none of this
   applies and the two-column list further up is what renders, untouched. The
   story is an enhancement layered on that layout, never a replacement for it.

   Prefix: everything new here is .rc-*, so nothing collides with the rules
   above even where it sits on the same elements. */

.rc-hint-scroll { display: none; }
.rc-story-on .rc-hint-tap { display: none; }
.rc-story-on .rc-hint-scroll { display: block; }

/* The hero holds still and dissolves rather than scrolling off the top.
   Fixed, not sticky: sticky is still laid out in the flow, so the section
   under it starts a screen down the document and slides up into place as the
   hero goes -- which is the movement this is meant to remove. Taken out of
   flow, the features sit pinned at the top from the first frame and are
   simply uncovered.

   Hence the opaque --paper ground: for most of the fade this element is the
   only thing standing between the reader and the feature panel already in
   position behind it.

   .rc-hero-pin is a second class, set by app.js only when it is going to
   drive the fade. Nothing here may apply without something to fade it out:
   a fixed hero at full opacity that never clears would cover the page.
   Under reduced motion the class is never set and the hero scrolls. */
.rc-story-on.rc-hero-pin .hero {
  /* STICKY, not fixed. `fixed` positions against the VIEWPORT, and the
     viewport is no longer the canvas -- body is, and body scrolls inside a
     taller window. A fixed hero would hold still against the browser window
     while the page moved underneath it inside the frame. Sticky pins it to
     the top of the scroller, which is body, which is the canvas.
     It stays stuck for the whole run rather than releasing, which is what
     `fixed` did too: the zoom takes it to opacity 0 and pointer-events none
     over the first frame, so what it does afterwards is not visible. */
  position: sticky;
  top: 0;
  width: 100%;
  min-height: 0;
  /* The frame token, not 100%: off the canvas host body has no height of its
     own, so a percentage has nothing to resolve against and the hero
     collapses to its content. */
  height: var(--rc-frame-h);
  /* No negative margin pulling this back out of the flow. It took one, and a
     percentage margin resolves against the container's WIDTH -- so -100%
     here would have been -1080px, not -565px. The story gives up a frame
     instead (see its height), which comes to the same total and needs no
     unit that does not exist. */
  /* Its content is laid out to a full screen and overflows 565px -- the foot
     paragraph was bleeding out of the bottom of the canvas and over the
     feature panel. The frame is a frame. */
  overflow: hidden;
  z-index: 2;
  background: var(--paper);
}

/* One stage per feature, plus two. --rc-stages is written by app.js from the
   real button count, so adding a seventh feature lengthens the scroll on its
   own rather than leaving the last one unreachable.

   The second extra stage is the hero's. The panel is pinned and showing
   feature 01 from the first frame, underneath the hero; that leading screen
   of scroll is the hero dissolving off it. Without it the hero would spend
   feature 01's turn sitting on top of it, and the first feature would get a
   fraction of the time every other one gets.

   The + 1 is what keeps the panel still. A sticky element is only pinned for
   its container's height MINUS its own, so at exactly six stages the pin ran
   out one screen early and the last feature slid up and off while it was
   still the one showing -- the one moment in the section that looked like
   ordinary scrolling. The extra screen is the room the panel needs to leave
   in, after the sixth feature has had its full turn. */
.rc-story-on .rc-story {
  position: relative;
  /* +2: the hero's frame, and the room to leave in -- both, as above. This
     was +1 on the reasoning that the sticky hero occupies the first frame
     itself, but the margin-top below pulls the story back up over that
     frame, so the hero's frame came out of this element's height after all
     and the pin was one frame short. Measured: the panel unpinned at
     3393px of a 565px frame, which is exactly where feature 06's stage
     STARTS (3390-3955), so Friends cross-faded in while the whole panel slid
     up off the canvas and never got a pinned turn -- the HYROX -> Friends
     change was the one that moved, and the stats arrived over a half-faded
     title. At +2 the pin runs to 3955, the end of the last stage.
     An explicit length and NOT a percentage: this element's parent is the
     .what section, which has no height of its own, so a percentage height
     has nothing to resolve against and collapses to the content -- written
     as 100% the whole story came out 571px instead of seven frames, and the
     six stages piled up on top of each other. */
  height: calc((var(--rc-stages, 6) + 2) * var(--rc-frame-h));
  /* Pulled up over the hero's frame. The hero is sticky and in the flow, so
     without this you scroll a whole frame past it before the story's top
     reaches the top of the canvas -- and because the hero is pinned and
     opaque for that frame, nothing moves. One dead screen at the very start.
     Overlapping them puts the zoom back on the first pixel of scroll, where
     it was when the hero was position:fixed. */
  margin-top: calc(var(--rc-frame-h) * -1);
}

/* The rail of marker elements that used to live here is gone. Stages were
   decided by an IntersectionObserver watching a line across the middle of the
   viewport, which needed a real element per stage to watch; ScrollTrigger
   reads the section's own progress, so there is nothing left to observe and
   nothing to keep in step with it. */

/* svh, not vh: on mobile the URL bar collapsing mid-scroll changes vh, which
   would resize the pinned panel underneath the visitor's finger. */
/* The pinned panel is the wrapper now, not the grid, so the strip rides
   along as a fixed header above it instead of being left behind in the
   scroll. */
.rc-story-on .rc-story-view {
  position: sticky;
  top: 0;
  /* One frame. Not 100%, which here would be 100% of .rc-story -- seven of
     them. */
  height: var(--rc-frame-h);
  display: flex;
  flex-direction: column;
}
/* No align-items here: the columns have to stretch the full height or the
   hairline between them runs for the height of the title and stops. Each
   column centres its own contents instead. minmax-style min-height: 0 for
   the same reason the mobile rows need it -- the handset's layout box is its
   unscaled height, and the grid has to be allowed under it. */
.rc-story-on .what-grid {
  flex: 1;
  min-height: 0;
}

/* All six names stack into one grid cell and cross-fade, so the column reads
   as a single title changing rather than a list with one row lit. The hints
   are not given a cell, so auto-placement drops them into the rows below. */
.rc-story-on .feature-list {
  display: grid;
  grid-template-columns: 1fr;
  align-content: center;
  gap: 0;
}
/* Opacity only -- nothing travels. A title that slid into place read as the
   page scrolling under a window, which is the one impression this section is
   built to avoid: the panel is meant to sit still while its contents change.
   So the outgoing title fades out where it stands and the next fades up in
   the same spot. */
.rc-story-on .feature {
  grid-area: 1 / 1;
  flex: none;
  border-bottom: none;
  background: transparent;
  cursor: default;
  /* visibility, not opacity alone: an opacity-0 button is still in the tab
     order, so a keyboard user would land on five titles they cannot see.
     GSAP's autoAlpha writes exactly this pair, which is why it drives them. */
  opacity: 0;
  visibility: hidden;
  /* NO transition. These two declarations are the resting state and the
     no-motion fallback, and nothing else: with motion on, app.js scrubs the
     fade to the scroll instead.

     They used to be a CSS transition -- out over 0.2s, in over 0.3s after a
     0.2s delay -- and that was what made the change between features look
     abrupt. The fade ran on a HALF-SECOND TIMER while everything around it
     ran on the scroll, so the two never agreed: stop mid-change and the timer
     carried on without you, scroll quickly and the handset had arrived at the
     next feature while the title was still a fifth of a second behind. And
     because the delay had to clear the outgoing title before the incoming one
     started -- all six share this one grid cell, and fading them together
     draws two at half strength, which at this size reads as doubled
     letterforms -- there was a beat with nothing in the column at all,
     however slowly you were moving.

     Scrubbed, the same sequence has no timer in it. The column is empty only
     at the exact boundary, which has no duration: the outgoing title reaches
     zero where the incoming one starts from zero. */
  transition: none;
  /* Promote the block to its own compositor layer so a change of opacity is
     the GPU blending a finished layer, not the browser re-rasterising a
     60px headline and three lines of body copy on every frame. A repaint
     that heavy cannot keep up, and what it cannot keep up with it drops --
     which is the other half of why this fade looked like it was stepping.
     Six elements, each one screen of the page: a cheap promotion, and the
     only place on this site that needs one. */
  will-change: opacity;
}
.rc-story-on .feature.is-active {
  opacity: 1;
  visibility: visible;
  /* The list style fills the active row with ink. One title alone on white
     does not want a black slab behind it. */
  background: transparent;
  color: var(--ink);
  transition: none;
}
.rc-story-on .feature-head { gap: 20px; }
.rc-story-on .feature-title { font-size: clamp(34px, calc(var(--rc-vw) * 4.6), 68px); }
/* The story shows one feature at a time against a whole panel, so the line
   can be a size the list has no room for. Narrower measure than the list's
   52ch, not wider: the type is bigger here, and the column is the same
   width. */
.rc-story-on .feature-desc {
  max-width: 44ch;
  font-size: clamp(15px, calc(var(--rc-vw) * 1.2), 19px);
  line-height: 1.5;
  margin-top: 6px;
}
/* The story's active row stays transparent rather than filling with ink, so
   the inverted colour the list uses would be white on white here. Back to
   the same grey the inactive rows carry. */
.rc-story-on .feature.is-active .feature-desc { color: var(--muted); }

/* The handset swaps screens by display, which cannot be transitioned, so the
   arriving screen fades itself up instead of cutting in hard against the
   title fading beside it. The one leaving is simply gone -- with the panel
   holding still, a plain fade-in is enough to read as the next feature
   arriving rather than the page having moved. */
@keyframes rc-screen-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
/* Delayed to match the title, so the handset and the name arrive together
   rather than the screen cutting in while the old title is still fading. */
.rc-story-on .what .screen.is-active { animation: rc-screen-in 0.3s ease 0.2s both; }

.rc-story-on .phone-stage { height: 100%; }
/* Scaled rather than resized: the handset's 640px screen is a fixed height
   the six mock screens are laid out against, so shrinking it for a short
   window has to happen after layout, not to it. */
/* scale() shrinks what is drawn and not what is occupied, so a scaled
   handset still claimed its full 662px and the flex centring was really
   centring an overflow -- padding at the foot of the column moved the
   drawing by half of what it should, and the lines down there ended up on
   the bezel. The negative margin takes back exactly the height the scale
   stops using (662px is the handset: a 640px screen in 11px of bezel), so
   the box it occupies matches the box it paints. Anchored at the top so the
   two line up. */
/* TWO scales, composed, and they must stay separate.
   --rc-phone-scale is the LAYOUT one: it is set by the height queries below
   and reconciled by the margin, and it must not move while a reader scrolls.
   --rc-phone-settle is the per-feature one app.js scrubs, which is transient
   and reconciled by nothing. Writing `transform` from GSAP instead would
   overwrite the whole property and take the layout scale with it -- on a
   short screen the handset would snap back to full size and run off the foot
   of the panel. A multiplier and an offset it composes cannot do that: with
   the defaults below this rule paints exactly what it painted before.
   --rc-phone-dx / -dy are the third pair, and transient the same way: the
   slide app.js plays when the race watch arrives or leaves beside the
   handset (see rcSyncWatch). Ahead of scale() so they move it in screen
   pixels, which is what they are measured in. --rc-phone-flip is the same
   slide's size: in one column the handset shrinks to make room for the
   watch, and this plays that back from the old size instead of cutting. */
.rc-story-on .phone {
  transform: translate3d(var(--rc-phone-dx, 0px),
                         calc(var(--rc-phone-lift, 0px) + var(--rc-phone-dy, 0px)), 0)
             scale(calc(var(--rc-phone-scale, 1) * var(--rc-phone-settle, 1) * var(--rc-phone-flip, 1)));
  transform-origin: center top;
  margin-bottom: calc((var(--rc-phone-scale, 1) - 1) * 662px);
}
/* SIZED FROM THE FRAME, AND SET BY app.js -- see sizePhone() there.
   This was a ladder of @media (max-height: ...) rungs, and those read the
   VIEWPORT, which stopped being the thing the handset lives in the moment
   the canvas became a fixed 1.91:1 window: an 800px-tall browser was handed
   the full-size 0.88 phone for a frame only 565px tall and the handset ran
   out of the bottom of the canvas.
   It cannot be done here. The scale wants (frame height - chrome) / 662,
   and CSS calc cannot divide a length by a length to get the unitless number
   scale() needs -- written that way it yields a LENGTH, scale() rejects it,
   and the phone silently renders unscaled. Which it did.
   The value below is only the starting point app.js overwrites. */
.rc-story-on .phone { --rc-phone-scale: 0.62; }

@media (max-width: 720px) {
  /* THE WATCH NEEDS A COLUMN, AND A COLUMN NEEDS HEIGHT.
     As a flex sibling the watch cannot work at this width: it is 186px next
     to a 322px phone, and at 375px the pair do not fit on a line -- the
     watch dropped underneath, the stage grew to 520px in a row with nothing
     like that to give, and the handset was shoved up to y=148 through a
     title sitting at 110-243 while the watch ran off the bottom of the
     screen across the scroll cue.

     Overlapping the watch onto the phone was the first fix and it half
     worked: the device read fine layered over the bezel, but .watch-note
     went with it, and that note is grey body copy sized for a white page --
     the moment it crossed onto a black screen it was unreadable. It is also
     the only thing on this site that says the watch app is "In design, not
     shipped", so it is a disclosure and not the part to sacrifice.

     What works is stacking them and giving the handset less room, which is
     what the min-height block below does. Hidden here, shown there: on a
     667px screen the stack does not fit at any size worth reading -- the
     phone came out at 189-440 through the title, and the watch 63px past the
     scroll cue. That is not a tuning problem, it is a screen with a title, a
     description and two devices to hold and room for three of them. */
  .watch-rig { display: none; }
  /* 0.55 of these is ~11px and ~11.5px, which is the size they are on the
     wide layout. Change one, change the other. */
  .watch-note { font-size: 20px; line-height: 1.4; margin-top: 10px; }
  .watch-note b { font-size: 21px; margin-bottom: 2px; }

  /* One column: the title rides above the handset instead of beside it.
     minmax(0, 1fr), never plain 1fr: scale() shrinks what the handset DRAWS
     and not what it occupies, so the row still asks for the full 662px of
     unscaled phone. A 1fr row cannot go under its min-content height, so on
     a short phone the two rows totalled more than the 100svh grid and the
     overflow went upward -- the title ended up above the top of the screen
     while the handset looked perfectly placed. Letting the row shrink costs
     nothing visually, because what is on screen is the scaled phone. */
  .rc-story-on .what-grid { grid-template-rows: auto minmax(0, 1fr); align-content: center; }
  .rc-story-on .phone-stage { min-height: 0; }
  .rc-story-on .feature-list { align-content: start; padding-top: 10px; }
  .rc-story-on .feature { padding-top: 6px; padding-bottom: 6px; gap: 6px; }
  .rc-story-on .feature-title { font-size: clamp(28px, calc(var(--rc-vw) * 7.5), 44px); }
  /* The title rides above the handset in one column here, so every line the
     description takes comes straight out of the phone's height. Held to the
     list's size and given no measure cap -- the column is already narrow. */
  .rc-story-on .feature-desc { font-size: 14px; line-height: 1.45; max-width: none; margin-top: 2px; }
  .rc-story-on .hint { padding-top: 6px; padding-bottom: 0; }
  /* The bottom padding is the scroll cue's room. The cue is pinned 14px up
     from the foot of the panel and is ~18px tall, and sizePhone() fits the
     handset to this content box -- at 10px the box ran under the cue and the
     handset sat on "Scroll down" (10px of it at 360x780). */
  .rc-story-on .phone-stage { padding-top: 10px; padding-bottom: 42px; }
  /* The scale itself is not set here -- see the narrow ladder below. */
}

/* Tall enough for both. 780px, because at 812 the stack lands with 4px under
   it and every rung below that was overlapping something. */
@media (max-width: 720px) and (min-height: 780px) {
  /* .rc-watch-on is set on the root by app.js exactly while the watch is on
     screen, and taken off when the race ends or the reader moves on -- so
     every other feature, and this one for most of its loop, keeps the
     full-size handset.

     COMPOUND, not descendant: .rc-watch-on and .rc-story-on are both set on
     <html>, so `.rc-watch-on .rc-story-on` asks for one inside the other and
     matches nothing at all. Written with a space it failed silently -- the
     handset kept the ladder's scale and overflowed the panel exactly as
     before. */
  .rc-watch-on.rc-story-on .phone-stage {
    flex-direction: column;
    gap: 4px;
    justify-content: center;
  }
  /* The handset's size while the watch is out is NOT set here. A
     `--rc-phone-scale: 0.44` sat here and never applied: app.js writes the
     scale inline, which beats any rule, so the handset stayed full size
     and the stacked pair overflowed the column -- over the description and
     off the bottom of the screen. sizePhone() now measures the watch and
     gives the handset what is left. */
  .rc-watch-on .watch-rig {
    display: block;
    width: 186px;
    transform: scale(0.54);
    /* Top centre, so the watch hangs from where the phone ends rather than
       growing away from it, and the negative margin takes back the height
       the scale stops using -- the same reconciliation the handset needs,
       for the same reason: scale() changes what is painted, never what is
       occupied. */
    transform-origin: top center;
    margin-bottom: calc((0.54 - 1) * 410px);
    /* rc-watch-in animates transform and ENDS on `none`, which would throw
       the scale away the moment the entrance finished -- the watch would
       appear correctly sized and then jump to full size. Same beat, opacity
       only, so the transform is left alone. */
    animation: rc-watch-fade .34s ease both;
  }
  @keyframes rc-watch-fade { from { opacity: 0; } to { opacity: 1; } }
  /* Set by sizePhone() when the column cannot hold both: under the watch the
     handset would have to drop below 0.4 -- 0.294 at 360x780, narrower than
     the watch itself. Same call as the 780px line above, made from the
     measured column instead of a guessed height, and the note goes with the
     watch, so nothing is left disclosing a device that is not on screen. */
  html.rc-watch-cramped .watch-rig { display: none; }
  /* 0.54 of these lands them at ~11px and ~11.5px, which is what they are on
     the wide layout. Change the scale, change these. */
  .rc-watch-on .watch-note { font-size: 20.4px; line-height: 1.4; margin-top: 8px; }
  .rc-watch-on .watch-note b { font-size: 21.3px; margin-bottom: 2px; }
}

/* A SECOND full ladder, for the one-column story only.
   Not a single width-only scale: the note this replaces was right that
   `@media (max-width: 720px) { --rc-phone-scale: … }` would win over the
   height queries by source order and shrink a TALL narrow screen while
   leaving a SHORT one too big. Every step below still carries a height,
   so the ladder above is extended rather than overridden -- each band is
   simply one notch smaller when the title sits above the handset instead
   of beside it.
   Why it became necessary: in one column the description is stacked on top
   of the phone rather than beside it, and every line of it comes out of the
   handset's height. Without this, a 390x844 screen kept the 0.88 phone and
   its foot ran down over `.hint-demo` -- the "Example data" line, which is
   the one piece of copy on this panel that is not allowed to be covered.
   If a description here gets longer, re-measure these; the constraint is
   that the phone's bottom edge clears .hint-demo's top. */
/* The one-column starting point, likewise overwritten by app.js: the title
   and description stack ABOVE the handset at this width rather than sitting
   beside it, so the phone gets what is left after them. */
@media (max-width: 720px) {
  .rc-story-on .phone { --rc-phone-scale: 0.42; }
}

/* The waitlist fades back in on arrival and stays in. One-shot: app.js stops
   observing it once it has landed, so it never fades back out. */
.rc-story-on .cta {
  opacity: 0;
  transform: translate3d(0, 26px, 0);
  transition: opacity 0.7s ease, transform 0.7s ease;
}
.rc-story-on .cta.is-in { opacity: 1; transform: none; }


@media (prefers-reduced-motion: reduce) {
  /* The layout stays -- it is what makes the six features reachable -- but
     every cross-fade lands instantly and nothing travels. The hero's
     scroll-linked fade is skipped in app.js rather than here. */
  .rc-story-on .feature,
  .rc-story-on .cta {
    transition-duration: 0s;
    transform: none;
  }
  /* Nothing to cancel on .feature any more -- its transition is none, and
     with motion off GSAP never runs, so the two declarations on it are
     the whole of how a title shows and hides here. Kept as the record
     that this block was asked about and found to need nothing. */
  .rc-story-on .what .screen.is-active { animation: none; }
  /* The one screen that animates on its own. With the cycle stopped each
     pane falls back to its own opacity -- 1 for the challenge screen, 0 for
     the friends pane -- so the handset simply holds the challenge screen,
     which is the half of this feature that is worth looking at. Not gated on
     .rc-story-on: the cycle runs in the plain list too. */
  .rc-ch-pane { animation: none; }
}

/* ---------- the real wordmark ---------- */
/* The brand was set in Archivo 800 -- close to the logo but not it. This is
   the actual mark (native/logo-full-master.png, trimmed to its glyphs and
   stored grey+alpha since the artwork is pure black, 376KB -> 43KB). The
   width/height attributes on the <img> are the file's real pixel size, so
   the box is reserved before it decodes and nothing jumps as it loads. */
/* The height goes on the image itself, not on an ancestor. It was on
   `.nav .brand`, which quietly missed the legal pages: their header is
   .legal-nav, so the rule never matched and a 1290px logo rendered at full
   size across the top of the privacy notice. Sizing the thing itself cannot
   miss a wrapper it does not know about. */
.brand { display: block; }
.brand-logo { display: block; width: auto; height: 19px; }
/* The wordmark in a subpage header is a link home. Not on index.html: its
   header lives inside the hero, which is fixed and fades to pointer-events
   none as you scroll, so a link there is only reachable when you are already
   at the top -- and a page you are on is not somewhere to navigate to. */
.brand-home { text-decoration: none; }
.brand-home:hover .brand-logo { opacity: 0.65; }
.footer .brand-logo { height: 15px; }

/* The closing wordmark. Its own full-width row in the footer's flex wrap.
   Capped at the artwork's own 1290px: past that it is being enlarged rather
   than displayed, and a logo is the last thing that should look soft. */
.footer .rc-wordmark {
  flex: 1 0 100%;
  height: auto;
  margin-bottom: 10px;
}
.footer .rc-wordmark .brand-logo { width: min(100%, 1290px); height: auto; }

/* ---------- scroll cue ---------- */
/* Sits at the foot of the pinned panel, centred, instead of stacked under
   the feature title. Only with the story on: without it the features are a
   list you click, and "scroll down" would be pointing at the wrong control. */
.rc-scroll-cue { display: none; }
/* Archivo, not the mono. The mono face is what this site uses for labels and
   metadata -- 11px, uppercase, widely tracked, deliberately recessive -- and
   the cue was reading as another caption instead of an instruction. Archivo
   is the face the headlines are set in, so at a real size and in full ink it
   reads as something addressed to the reader. */
.rc-story-on .rc-scroll-cue {
  display: block;
  position: absolute;
  left: 0;
  right: 0;
  bottom: 24px;
  text-align: center;
  font-family: Archivo, sans-serif;
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.02em;
  text-transform: none;
  color: var(--ink);
  /* Under the handset rather than over it: the panel's own children paint in
     order and the phone is interactive. */
  pointer-events: none;
}
/* The example-data line keeps its place in the markup -- so with no JS it
   still sits in the column where it always did -- and is lifted out to the
   foot of the panel only while the story is running, clear of the title. It
   is left-aligned against the centred cue so the two never meet. */
.rc-story-on .hint-demo {
  position: absolute;
  left: 0;
  bottom: 22px;
  width: auto;
  padding-top: 0;
  padding-bottom: 0;
  margin: 0;
}

/* The arrow travels further and faster than it did, and sits on its own line
   under the words rather than trailing them, so the movement is the thing
   that catches the eye rather than a character drifting at the end of a
   sentence. */
.rc-scroll-cue::after {
  content: "↓";
  display: block;
  margin-top: 2px;
  font-size: 20px;
  line-height: 1;
  animation: rc-cue-nudge 1.4s ease-in-out infinite;
}
@keyframes rc-cue-nudge {
  0%, 100% { transform: translateY(0); opacity: 1; }
  50% { transform: translateY(8px); opacity: 0.45; }
}

/* ---------- loading screen ---------- */
/* Leaves on its own from CSS. app.js only ever brings that forward, so a
   script that never runs cannot strand a visitor behind a white sheet. */
.rc-loader {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--paper);
  animation: rc-loader-out 0.62s cubic-bezier(0.65, 0, 0.35, 1) 1.9s forwards;
}

/* On the canvas, the sheet is the canvas -- inset:0 covered the whole window
   and made the white margins look like part of the site while it was up. */
@media (min-width: 721px) {
  html.rc-canvas .rc-loader {
    inset: 0 auto auto 50%;
    margin-left: calc(var(--rc-frame-w) / -2);
    width: var(--rc-frame-w);
    height: var(--rc-frame-h);
  }
}
/* A DIFFERENT animation-name from the rule above, and that is the whole
   point of it -- the two keyframe blocks are deliberately identical.
   Changing any other animation property while the name stays the same does
   not restart an animation, it just re-times the one already running. So
   when this class landed at 1.5s, all it did was strip the 1.9s delay off an
   animation whose 0.62s had "already elapsed", and the loader jumped to its
   finished state in a single frame instead of lifting. A new name is a new
   animation, which starts at nought.

   Do not merge these two blocks back into one. */
.rc-loader.is-done { animation: rc-loader-lift 0.62s cubic-bezier(0.65, 0, 0.35, 1) forwards; }
/* Pulled up and off the top rather than dissolved, so the site is uncovered
   from the bottom edge of the screen upwards -- the loader behaves like a
   sheet being lifted off it. It stays fully opaque the whole way: fading it
   at the same time would show the page through it before it had gone, which
   is the thing the movement is there to avoid. visibility at the end so a
   sheet parked off screen cannot take a click meant for the page. */
@keyframes rc-loader-out {
  to { transform: translateY(-100%); visibility: hidden; }
}
/* The same lift under a second name, for the reason given on .is-done above:
   the name is what makes it a fresh animation rather than a re-timed one. */
@keyframes rc-loader-lift {
  to { transform: translateY(-100%); visibility: hidden; }
}
.rc-loader-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 26px;
  padding: 24px;
}
.rc-loader-logo { width: min(260px, calc(var(--rc-vw) * 54)); height: auto; }

.rc-ghost { width: min(190px, calc(var(--rc-vw) * 42)); height: auto; overflow: visible; }
.rc-ghost-body { fill: var(--ink); }
.rc-ghost-face { fill: var(--paper); }
.rc-ghost-brow { stroke: var(--paper); }
/* --meta, not --hair: at #e6e6e6 the trails were all but invisible on white
   and the ghost just hung there. These are the only thing saying it is
   moving. */
.rc-ghost-speed { stroke: var(--meta); }
/* Running on the spot: the body strides, the speed lines stream past it.
   Two strides per cycle rather than one bob, because a single rise and fall
   reads as hovering -- the thing has no legs, so the stride pattern is all
   there is to say it is running rather than floating. Each stride lands
   squashed and leaves the ground stretched, and the lean deepens on the push
   and eases at the top. Amplitude is up from 4px to 9: at the size this
   draws, 4px in a 150-unit viewBox was a shimmer. */
.rc-ghost-run {
  transform-box: view-box;
  transform-origin: 85px 120px;
  animation: rc-ghost-run 0.62s ease-in-out infinite;
}
@keyframes rc-ghost-run {
  0%   { transform: translate(30px, 7px) rotate(15deg) scale(1.04, 0.95); }
  25%  { transform: translate(33px, -9px) rotate(10deg) scale(0.97, 1.05); }
  50%  { transform: translate(30px, 6px) rotate(14deg) scale(1.03, 0.96); }
  75%  { transform: translate(27px, -8px) rotate(11deg) scale(0.98, 1.04); }
  100% { transform: translate(30px, 7px) rotate(15deg) scale(1.04, 0.95); }
}
/* Held at full strength across the middle of each line's pass and staggered
   by a third of the cycle, so one is always streaking -- they used to fade
   up and straight back down, which flickered instead of streamed. */
.rc-ghost-speed line { animation: rc-ghost-streak 0.45s linear infinite; }
.rc-ghost-speed line:nth-child(2) { animation-delay: 0.15s; }
.rc-ghost-speed line:nth-child(3) { animation-delay: 0.3s; }
@keyframes rc-ghost-streak {
  0% { opacity: 0; transform: translateX(20px); }
  20% { opacity: 1; }
  75% { opacity: 1; }
  100% { opacity: 0; transform: translateX(-18px); }
}

@media (max-width: 720px) {
  /* One column, so left-aligned and centred are the same column of space:
     the two lines are stacked instead of set side by side. */
  .rc-story-on .hint-demo { bottom: 48px; right: 0; }
  .rc-story-on .rc-scroll-cue { bottom: 14px; font-size: 15px; }
  /* Arrow back on the words' own line here. Stacked it makes the cue about
     41px tall, and on a 667px screen there is only ~80px under the handset
     for this and the example-data line together -- the two ran into each
     other. Inline it is one line again and both fit. */
  .rc-story-on .rc-scroll-cue::after {
    content: " ↓";
    display: inline-block;
    margin-top: 0;
    font-size: 16px;
  }
}

/* On a short screen the two lines at the foot of the panel are sitting where
   the handset already is -- the grey text landed on the black bezel. The
   padding goes on the columns rather than on the grid so that the hairline
   between them, which is a border on the column, still runs the full height
   instead of stopping short of the bottom.
   Two columns only. In one column there is no hairline and the description
   sits ABOVE the handset, so 74px under the title list and 74px under the
   handset were 148px taken out of the handset's height for nothing -- the
   old fixed-spare sizing simply let the handset run over that padding, and
   once the handset was fitted to the stage it came out at 0.435 instead of
   0.569 at 375x667. One column keeps the cue clear with its own padding. */
@media (max-height: 760px) and (min-width: 721px) {
  /* Top-aligned rather than centred. Centring only buys back half of any
     padding you add at the foot, so clearing the two lines by centring meant
     padding the column by twice the space actually needed. Pinned to the top
     of its row, the handset ends where it ends and the rest of the row is
     free. */
  .rc-story-on .phone-stage {
    align-items: flex-start;
    padding-bottom: 74px;
  }
  .rc-story-on .feature-list { padding-bottom: 74px; }
}

@media (prefers-reduced-motion: reduce) {
  /* The screen still goes, and sooner -- what it must not do is run on the
     spot in front of someone who asked for no motion. */
  /* .is-done has to be named here too. It is a two-class selector, so it
     outweighs a bare .rc-loader no matter that this block sits in a media
     query -- a media query adds no weight of its own. Without it, the moment
     the script added that class the sheet went back to travelling for 0.62s
     in front of someone who had asked for no motion. */
  .rc-loader,
  .rc-loader.is-done { animation: rc-loader-out 0.01ms linear 0.4s forwards; }
  .rc-ghost-run,
  .rc-ghost-speed line,
  .rc-scroll-cue::after { animation: none; }
  .rc-ghost-run { transform: translate(30px, 4px) rotate(13deg); }
}

/* ---------- pricing ---------- */
/* Built on .legal's column, so the page inherits the measure, the type sizes
   and the footer the policy pages already use. Only the plan blocks are new. */
.rc-pricing .rc-plans {
  display: grid;
  /* minmax(0, 1fr), not 1fr: a price is one unbreakable token, so a bare 1fr
     track refuses to go below its content and the pair overflows the column
     on a narrow screen instead of stacking. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: 18px;
  margin: 26px 0 0;
}
.rc-plan {
  border: 1px solid var(--hair);
  border-radius: 18px;
  padding: 24px;
  display: flex;
  flex-direction: column;
}
/* The ink border is the only thing marking Pro out. No "most popular" flag
   and no crossed-out price: there is no previous price, and nothing has been
   sold yet for anything to be popular. */
.rc-plan--pro { border-color: var(--ink); border-width: 2px; padding: 23px; }
/* Qualified by .rc-pricing so it outweighs `.legal p`, which is what this
   badge is. Unqualified it lost the colour and set near-black on black --
   #2a2a2a on #0a0a0a, which is a badge you cannot read. */
.rc-pricing .rc-plan-badge {
  align-self: flex-start;
  margin: 0 0 10px;
  padding: 5px 10px 4px;
  background: var(--ink);
  color: #fff;
  font-family: "JetBrains Mono", monospace;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.rc-pricing .rc-plan-name { font-size: 21px; font-weight: 800; margin: 0; }
.rc-pricing .rc-plan-price {
  font-size: clamp(34px, calc(var(--rc-vw) * 5), 46px);
  font-weight: 900;
  letter-spacing: -0.04em;
  line-height: 1.1;
  margin: 6px 0 0;
  font-variant-numeric: tabular-nums;
}
.rc-plan-per { font-size: 15px; font-weight: 600; letter-spacing: 0; color: var(--muted); }
.rc-pricing .rc-plan-line { margin: 8px 0 0; color: var(--muted); font-size: 15px; }
.rc-pricing .rc-plan-list { margin: 16px 0 0; }
.rc-pricing .rc-plan-list li { font-size: 15.5px; }
/* The one line in the Free column describing something it does NOT include.
   Muted rather than struck through: there is no former price or former
   feature here to cross out, and a strikethrough would imply one. */
.rc-pricing .rc-plan-no { color: var(--muted); }

.rc-annual {
  margin: 26px 0 0;
  padding: 24px;
  border: 1px solid var(--hair);
  border-radius: 18px;
  background: var(--soft);
}
.rc-pricing .rc-annual h2 { margin: 0; font-size: 19px; }
.rc-pricing .rc-annual-note { margin: 8px 0 0; font-size: 15.5px; color: var(--muted); }
.rc-pricing .rc-plan-avg { margin: 4px 0 0; font-size: 14px; color: var(--muted); }
.rc-pricing .rc-pricing-note { margin: 4px 0 0; font-size: 15px; color: var(--muted); }

/* The waitlist button on its own, centred, now the paragraph that used to
   explain it is gone. */
/* Quiet, and under the plans rather than beside a price: it is a unit, not
   a caveat, and a reader only needs it once. Qualified with .rc-pricing for
   the same reason every other rule in this block is -- .legal p would
   otherwise win on specificity and set it back to body size. */
.rc-pricing .rc-price-note { margin: 22px 0 0; text-align: center; font-size: 13.5px; color: var(--muted); }
.rc-pricing-cta { margin: 30px 0 0; text-align: center; }

/* Monthly / Yearly. Hidden until the script says it works -- an inert
   segmented control is worse than no control. */
.rc-billing { display: none; }
/* Centred in the column. flex with a fit-content width rather than
   inline-flex in a centred parent, so the centring belongs to the control
   and does not depend on the text-align of whatever it sits in. */
.rc-billing-on .rc-billing {
  display: flex;
  width: fit-content;
  margin: 22px auto 0;
  padding: 3px;
  border: 1px solid var(--hair);
  border-radius: 999px;
}
.rc-billing-opt {
  appearance: none;
  border: none;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  padding: 9px 18px;
  border-radius: 999px;
  font-family: "JetBrains Mono", monospace;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.rc-billing-opt.is-on { background: var(--ink); color: #fff; }
/* With the toggle live the yearly card is a second copy of what the Pro card
   is already showing, so it goes. */
.rc-billing-on .rc-annual { display: none; }
