/* ==========================================================================
   Torvex — torvexapp.app

   Every colour here is lifted verbatim from `src/theme/palette.ts`, and the
   spacing, radius, type and tracking scales from `src/theme/index.ts`. The
   site is the app's design system rendered in CSS, not an approximation of
   it: if a value looks arbitrary, it is because it is the app's value.

   THE 2026-10 REDESIGN. The app moved to one design project in October:
   neutral greys with a pale periwinkle accent in dark and a cobalt one in
   light, cards on the canvas instead of a blue plane, and three typefaces —
   Familjen Grotesk for everything, Geist Mono for the small capitals and the
   columns of figures, Newsreader for the fixed words that title a screen.
   This file follows it token for token, and the app's own test
   (`tests/sitePalette.test.ts`) fails when the two drift apart.

   Dark is the default because that is the theme a lifter already knows. An
   inline script in <head> stamps `data-theme` before first paint, so the
   system preference decides without a flash; with JavaScript off the page
   stays dark.
   ========================================================================== */

/* --- The typefaces — `src/theme/fonts.ts` ----------------------------------
 *
 * The app's own files, as WOFF (tools/fonts.js deflates the TTFs the app ships;
 * the glyphs are byte for byte the same) with their OFL licences beside them.
 * One family name per typeface and the weight on `font-weight`, as the app does
 * it, so a style that only changes the size can never undo the weight. `swap`:
 * the words are on screen at once in the fallback and the face arrives over
 * them, rather than a blank page waiting on a font. */

@font-face {
  font-family: 'Familjen Grotesk';
  src: url('fonts/FamiljenGrotesk_400Regular.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Familjen Grotesk';
  src: url('fonts/FamiljenGrotesk_500Medium.woff') format('woff');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Familjen Grotesk';
  src: url('fonts/FamiljenGrotesk_600SemiBold.woff') format('woff');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Familjen Grotesk';
  src: url('fonts/FamiljenGrotesk_700Bold.woff') format('woff');
  /* 700 and up: the app asks for 800 in a few places and the platform rounds
     it to the Bold the family has. A range says the same thing to a browser. */
  font-weight: 700 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Geist Mono';
  src: url('fonts/GeistMono_400Regular.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Geist Mono';
  src: url('fonts/GeistMono_500Medium.woff') format('woff');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Geist Mono';
  src: url('fonts/GeistMono_600SemiBold.woff') format('woff');
  font-weight: 600 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Newsreader';
  src: url('fonts/Newsreader_400Regular.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* --- The palettes ------------------------------------------------------- */

:root {
  /* surfaces */
  --bg: #26272b;
  /* The fill of a control on a sheet — a step LIGHTER than both the sheet and a
     card, as iOS's own dark fields are. */
  --bg-elevated: #45464c;
  --surface: #37383d;
  /* The design's `tray`: a segmented track, an input's fill, a row of figures. */
  --surface-alt: #313237;
  --surface-pressed: #4f5056;

  /* rims */
  --border: rgba(240, 241, 245, 0.18);
  --border-strong: rgba(240, 241, 245, 0.42);

  /* ink */
  --text: #f7f7f8;
  --text-2: #d0d1d6;
  --text-3: #b7b8bf;
  --text-disabled: #7a7b82;

  /* accents — the design's pale periwinkle, LIGHTER when pressed */
  --primary: #b3c3ff;
  --primary-pressed: #c6d2ff;
  --primary-rim: #d3dcff;
  --primary-soft: #414868;
  --primary-dim: #7c88be;

  --success: #9ed9a9;
  --success-soft: rgba(158, 217, 169, 0.16);
  --warning: #f0be74;
  --warning-soft: rgba(240, 190, 116, 0.16);
  --danger: #ff9f8c;
  --danger-soft: rgba(255, 159, 140, 0.16);
  --danger-rim: #ffc3b7;
  --gold: #e8bc62;
  --gold-soft: #4e3f22;
  /* The one hue the core palette does not carry — the drop-set badge and the
     fat macro bar share it. */
  --violet: #a855f7;

  /* `muscleColors`, for the initials plate of an exercise without artwork.
     Fixed hues in the app too — a chart legend does not flip with the theme. */
  --muscle-chest: #3b82f6;
  --muscle-shoulders: #f59e0b;

  /* `surfaceHero` and its family. The blue plane they painted is gone from the
     app's screens; the nudge banner is what still wears them. */
  --hero: #343a55;
  --hero-raised: #414868;
  --hero-border: #5a6390;
  --on-hero: #a6b0d6;
  --primary-light: #c6d2ff;

  /* glass */
  --glass-bar: rgba(55, 56, 61, 0.82);
  /* The body of a sheet or a dialog is the CANVAS tone, so a card on it reads as
     it does on the page and a control has a step to stand on. */
  --glass-panel: rgba(55, 56, 61, 0.66);
  --glass-panel-strong: rgba(38, 39, 43, 0.96);
  /* `glassPillBody` and `glassPillRim` — the resume pill's and the share disc's. */
  --glass-pill: rgba(61, 62, 67, 0.66);
  --pill-rim: rgba(240, 241, 245, 0.2);
  --glass-rim: rgba(240, 241, 245, 0.16);
  /* `overlay` — the scrim under a sheet or a dialog. */
  --overlay: rgba(0, 0, 0, 0.55);
  --glass-highlight: rgba(255, 255, 255, 0.22);
  /* `primaryGlass` — the tinted lens a blue control is under Liquid Glass. */
  --primary-glass: rgba(179, 195, 255, 0.45);

  /* Ink on a FILL — navy here, because the dark theme's fills (the accent,
     danger, success) are pale pastels; white in the light theme, where they are
     deep. Anything on a photo or a scrim takes `--on-dark` instead. */
  --on-filled: #131a35;
  --on-dark: #ffffff;

  --shadow-card: 0 4px 12px rgba(0, 0, 0, 0.3);
  --shadow-float: 0 18px 60px rgba(0, 0, 0, 0.5);
  /* `shadow.floating` — what the rest timer and the tab bar cast in the app.
     Not per theme there either, so it is set once. */
  --shadow-floating: 0 -2px 20px rgba(0, 0, 0, 0.45);
  --phone-bezel: #121214;
  --phone-rim: #3a3b40;
  /* The Dynamic Island is a cutout, not a surface: black on a silver phone too. */
  --island: #000000;
  /* `demoPlate` — the white the exercise clips are rendered on, in both themes:
     "a deliberate plate, not a missing light value". */
  --demo-plate: #ffffff;
  /* The system tab bar's selected-item lens — a lift of the bar's own material,
     so a white wash at low alpha here and an ink wash in the light theme. */
  --tab-lens: rgba(255, 255, 255, 0.1);
  /* The faintest hover the page has — a wash over glass, for a row that is
     already a whole line of text and needs only to say "this one". */
  --wash: rgba(255, 255, 255, 0.05);

  --brand: #268de7; /* the app icon's own blue, which the redesign kept */

  /* type, from `fontFamily` */
  --font-sans: 'Familjen Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI',
    Roboto, 'Helvetica Neue', Arial, sans-serif;
  --font-mono: 'Geist Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
  --font-serif: 'Newsreader', Georgia, 'Times New Roman', serif;

  /* spacing, from `spacing` */
  --s-xs: 0.25rem;
  --s-sm: 0.5rem;
  --s-md: 0.75rem;
  --s-lg: 1rem;
  --s-xl: 1.25rem;
  --s-2xl: 1.5rem;
  --s-3xl: 2rem;

  /* radius, from `radius` — `xxl` is the redesign's card corner */
  --r-sm: 6px;
  --r-md: 10px;
  --r-lg: 14px;
  --r-xl: 20px;
  --r-xxl: 24px;
  --r-pill: 999px;

  /* tracking, from `tracking` — points converted to em at their design size */
  --tr-caps: 0.145em;
  --tr-tight: -0.02em;
  --tr-heading: -0.029em;
  --tr-display: -0.032em;

  /* motion, from `src/lib/motion.ts` — one vocabulary for the whole app, and so
     for the whole page. `fast` is a press or a tint change, `base` anything
     appearing or moving, `slow` a sheet or a screen-level reveal. The curve is
     the app's EASE_OUT; the rise and the stagger are its list entrance. */
  --d-fast: 130ms;
  --d-base: 220ms;
  --d-slow: 320ms;
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  /* `EASE_IN`, the literal inverse of the curve above — (x1, y1, x2, y2) reversed
     is (1 - x2, 1 - y2, 1 - x1, 1 - y1) — for the way OUT of anything that came in
     on EASE_OUT, so leaving is arriving played backwards rather than a second
     deceleration off-screen. */
  --ease-in: cubic-bezier(0.7, 0, 0.84, 0);
  --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
  --stagger: 45ms;
  --rise: 8px;

  --maxw: 1140px;
  --nav-h: 60px;
}

/*
 * THE LIGHT PALETTE: NEAR-WHITE AND NEUTRAL.
 *
 * The design project's light canvas was a warm #F2EFE9, and the owner turned the
 * tint down in both themes — "i dont want beige or brown, its a fitness app". So
 * every grey keeps the design's lightness with the warmth taken out: #F2F2F4
 * canvas, white cards that take their edge from a hairline, an #E8E8EC tray set
 * DOWN from the canvas so a track or a field reads as recessed. The accents are
 * the design's own: a deep cobalt with white ink on it. The values are
 * `palette.ts`'s, verbatim.
 */
:root[data-theme='light'] {
  color-scheme: light;

  --bg: #f2f2f4;
  --bg-elevated: #ffffff;
  --surface: #ffffff;
  --surface-alt: #e8e8ec;
  --surface-pressed: #dcdce1;

  --border: #c6c6cd;
  --border-strong: #80808a;

  --text: #18181b;
  --text-2: #52525b;
  --text-3: #63636d;
  --text-disabled: #a1a1aa;

  --primary: #2747c6;
  --primary-pressed: #1e3aa8;
  --primary-rim: #1a3398;
  --primary-soft: #e4e9f8;
  --primary-dim: #7488d8;

  --success: #127034;
  --success-soft: rgba(18, 112, 52, 0.12);
  --warning: #a04a08;
  --warning-soft: rgba(160, 74, 8, 0.12);
  --danger: #b3361f;
  --danger-soft: rgba(179, 54, 31, 0.1);
  --danger-rim: #932a16;
  --gold: #85570a;
  --gold-soft: #f3e5c4;

  --hero: #e4e9f8;
  --hero-raised: #ffffff;
  --hero-border: #a6b3e3;
  --on-hero: #4a5788;
  --primary-light: #1e3aa8;

  --glass-bar: rgba(250, 250, 251, 0.74);
  --glass-panel: rgba(250, 250, 251, 0.68);
  --glass-panel-strong: rgba(232, 232, 236, 0.96);
  --glass-pill: rgba(255, 255, 255, 0.74);
  --pill-rim: rgba(24, 24, 27, 0.12);
  --glass-rim: rgba(24, 24, 27, 0.1);
  /* A scrim stays DARK in a light theme — it is shade over the page, not a wash. */
  --overlay: rgba(24, 24, 27, 0.32);
  --glass-highlight: rgba(255, 255, 255, 0.55);
  --primary-glass: rgba(39, 71, 198, 0.3);

  --on-filled: #ffffff;

  --shadow-card: 0 4px 14px rgba(24, 24, 27, 0.06);
  --shadow-float: 0 18px 60px rgba(24, 24, 27, 0.14);
  --phone-bezel: #d6d6db;
  --phone-rim: #bdbdc4;
  --tab-lens: rgba(24, 24, 27, 0.07);
  --wash: rgba(24, 24, 27, 0.04);
}

:root {
  color-scheme: dark;
}

/* --- Reset -------------------------------------------------------------- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  /* `clip` rather than `hidden`, and on the root rather than on <body>:
     `overflow-x: hidden` on the body alone turns the body into its own scroll
     container in some engines, which is enough to break position: fixed. */
  overflow-x: clip;
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--nav-h) + 24px);
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 1.0625rem;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

img,
svg {
  display: block;
  max-width: 100%;
}

a {
  color: inherit;
  text-decoration: none;
}

h1,
h2,
h3,
h4,
p,
ul,
ol,
figure {
  margin: 0;
}

ul {
  padding: 0;
  list-style: none;
}

button {
  font: inherit;
  color: inherit;
  border: 0;
  background: none;
  cursor: pointer;
}

/* Every control here draws its own press state, so Safari's grey tap flash on
   top of it is a second, unrelated piece of feedback for the same touch.
   `manipulation` keeps pinch and pan and drops the double-tap-to-zoom wait,
   which is the one latency on the input path this page does not need. */
a,
button,
summary {
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}

/* No radius here. The outline already follows the element's own corners, and
   setting one on `:focus-visible` gave the pill toggle and the 10px buttons a
   6px corner for exactly as long as they had keyboard focus — a shape change
   as the reward for pressing Tab. */
:focus-visible {
  outline: 2px solid var(--primary-rim);
  outline-offset: 3px;
}

::selection {
  background: var(--primary-soft);
  color: var(--text);
}

/* --- Shared type -------------------------------------------------------- */

.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 0 var(--s-2xl);
}

/* The micro-caps, in Geist Mono as the app sets every one of them. */
.caps {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: var(--tr-caps);
  text-transform: uppercase;
  color: var(--text-2);
}


.caps--primary {
  color: var(--primary);
}

.caps--success {
  color: var(--success);
}

/*
 * THE SERIF, for the fixed words that title a screen.
 *
 * The app sets Newsreader on exactly one kind of text: the words that name a
 * screen — "Routines", "Profile", "Workout complete", the sign-in headings —
 * at 42pt, weight 400, leading 45, tracking -1pt. Never on a name (a routine, a
 * session, a meal), a figure or a label. The page's equivalent of a screen
 * title is its h1 and the h2 that names each section, so those two take it,
 * with the tracking scaled from 42pt (-1 / 42 ≈ -0.024em); a card's title and
 * everything else stay in the sans, as everything else in the app does.
 */
h1,
.h1 {
  font-family: var(--font-serif);
  font-size: clamp(2.5rem, 5.6vw, 4.125rem);
  line-height: 1.07;
  font-weight: 400;
  letter-spacing: -0.024em;
}

h2,
.h2 {
  font-family: var(--font-serif);
  font-size: clamp(1.875rem, 3.6vw, 2.625rem);
  line-height: 1.08;
  font-weight: 400;
  letter-spacing: -0.024em;
}

/* A heading breaks into lines of even length rather than a full line and a
   stranded word ("Ranks, achievements and your / circle"); running prose only
   refuses to end on a single word, which costs it nothing else. Both are
   progressive: an engine without them wraps as it always did. */
h1,
h2,
h3,
.h1,
.h2 {
  text-wrap: balance;
}

p,
li,
dd,
figcaption {
  text-wrap: pretty;
}

h3,
.h3 {
  font-size: 1.25rem;
  font-weight: 700;
  letter-spacing: var(--tr-tight);
}

.lede {
  font-size: clamp(1.0625rem, 1.5vw, 1.25rem);
  line-height: 1.6;
  color: var(--text-2);
}

.tnum {
  font-variant-numeric: tabular-nums;
}

/* --- Buttons — `src/components/ui/Button.tsx` ---------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-sm);
  min-height: 52px;
  padding: 0 var(--s-xl);
  /* A capsule — `shape="capsule"`, which is every forward action in the
     redesign: Start, Finish, Done, Get Pro. */
  border-radius: var(--r-pill);
  border: 1px solid transparent;
  font-size: 1.0625rem;
  font-weight: 600;
  white-space: nowrap;
  /* Colour may take its time; the press may not. Feedback on pointer-down
     is what makes a control feel direct, and `fast` — the app's own press and
     tint duration — is where it stops. The same `fast` on transform is the
     RELEASE: the button easing back up along the app's curve. */
  transition: background-color var(--d-fast) ease, border-color var(--d-fast) ease,
    opacity var(--d-fast) ease, transform var(--d-fast) var(--ease-out);
}

.btn--sm {
  min-height: 36px;
  padding: 0 var(--s-md);
  font-size: 0.8125rem;
}

.btn--md {
  min-height: 44px;
  padding: 0 var(--s-lg);
  font-size: 0.9375rem;
}

.btn--primary {
  background: var(--primary);
  border-color: var(--primary-rim);
  color: var(--on-filled);
}

@media (hover: hover) {
  .btn--primary:hover {
    background: var(--primary-pressed);
  }
}

.btn--secondary {
  background: var(--surface-alt);
  border-color: var(--border);
  color: var(--text);
}

@media (hover: hover) {
  .btn--secondary:hover {
    background: var(--surface-pressed);
  }
}

.btn--ghost {
  color: var(--primary);
}

@media (hover: hover) {
  .btn--ghost:hover {
    background: var(--surface-alt);
  }
}

/* The press itself is instant: a transform that eases DOWN over 100ms is a
   button that starts moving a beat after the finger lands, which is the lag
   that makes a control feel like a picture of one. Only the way back eases. */
.btn:active {
  transform: scale(0.97);
  transition: background-color var(--d-fast) ease, border-color var(--d-fast) ease,
    opacity var(--d-fast) ease, transform 0s;
}

.btn--full {
  width: 100%;
}

/* --- Cards — `src/components/ui/Card.tsx` -------------------------------- */

/* `radius.xxl` with a hairline: the redesign rings every card, so a white card
   still has an edge on the pale canvas and a charcoal one on the dark. */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xxl);
  padding: var(--s-xl);
}

/* --- Nav ---------------------------------------------------------------- */

.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 50;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  background: var(--glass-bar);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid var(--border);
}

@supports not (backdrop-filter: blur(1px)) {
  .nav {
    background: var(--bg-elevated);
  }
}

/*
 * A scroll edge, not a permanent divider.
 *
 * At the top of the page there is nothing under the bar but the hero plane,
 * and a glass strip with a hairline across the plane's top was a grey band
 * drawn over the one surface it had no reason to separate itself from. The
 * material exists to keep the links legible over CONTENT scrolling beneath
 * them — so it arrives when content does. Tint, blur and hairline come in
 * together over the first 64px of scroll, which is roughly where the hero's
 * eyebrow reaches the bar: a material materialising, not a colour fading.
 *
 * It is driven by scroll position, not by time, so it is a state and not an
 * animation in the vestibular sense, and it needs no script. Where the
 * engine has no scroll timelines the rules above stand and the bar is simply
 * always glass, which is what it was before.
 */
@supports (animation-timeline: scroll()) and (backdrop-filter: blur(1px)) {
  .nav {
    animation: nav-materialize linear both;
    animation-timeline: scroll(root);
    animation-range: 0 64px;
  }

  @keyframes nav-materialize {
    from {
      background-color: transparent;
      border-bottom-color: transparent;
      backdrop-filter: saturate(100%) blur(0px);
      -webkit-backdrop-filter: saturate(100%) blur(0px);
    }
    to {
      background-color: var(--glass-bar);
      border-bottom-color: var(--border);
      backdrop-filter: saturate(180%) blur(20px);
      -webkit-backdrop-filter: saturate(180%) blur(20px);
    }
  }
}

.nav__inner {
  display: flex;
  align-items: center;
  gap: var(--s-xl);
  width: 100%;
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 0 var(--s-2xl);
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 1.0625rem;
  font-weight: 700;
  letter-spacing: var(--tr-tight);
}

.brand__mark {
  width: 28px;
  height: 28px;
  border-radius: 8px;
  flex: 0 0 auto;
}

/* Full-contrast ink on the glass. `--text-2` is a secondary grey tuned for a
   solid canvas; over a translucent bar with content moving underneath it goes
   flat, which is the vibrancy problem Apple's materials guidance names. The
   padding turns a 15px line of text into a target a finger can actually hit. */
.nav__links {
  display: flex;
  align-items: center;
  gap: var(--s-lg);
  margin-left: auto;
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--text);
}

.nav__links a {
  padding: 0.65rem 0.25rem;
  border-radius: var(--r-sm);
  transition: color var(--d-fast) ease;
}

@media (hover: hover) {
  .nav__links a:hover {
    color: var(--primary);
  }
}

/* Down, not up: the colour lands on the press, with no transition to wait
   through, and the hover state is what the pointer leaves behind. */
.nav__links a:active,
.footer a:active {
  color: var(--primary);
  transition: none;
}

/* Where you are. The link for the section under the bar takes the accent, and
   keeps it as the section scrolls by — the one question a fixed bar can answer
   that the page itself cannot. Set by the script (`aria-current`), so it is
   also what a screen reader hears; without the script the bar simply has no
   current item, which is what it had before. */
.nav__links a[aria-current='location'],
.sheet__list a[aria-current='location'] {
  color: var(--primary);
}

.nav__actions {
  display: flex;
  align-items: center;
  gap: var(--s-md);
  margin-left: var(--s-lg);
}

/* --- The two round buttons: the theme switch and, on a phone, the menu -----
 *
 * 44px, not 40. It is the platform's minimum for a thing a finger presses,
 * and it is also the height of the `btn--md` beside them, so the three
 * controls in the bar sit on one line rather than two. */
.themetoggle,
.menu__btn {
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border-radius: var(--r-pill);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-2);
  transition: color var(--d-fast) ease, background-color var(--d-fast) ease,
    transform var(--d-fast) var(--ease-out);
}

@media (hover: hover) {
  .themetoggle:hover,
  .menu__btn:hover {
    color: var(--text);
    background: var(--surface-alt);
  }
}

.themetoggle:active,
.menu__btn:active {
  transform: scale(0.94);
  transition: color var(--d-fast) ease, background-color var(--d-fast) ease, transform 0s;
}

/* Only on a phone; the bar carries the links themselves above 900px. The
   browser sets `aria-expanded` on it for the popover it targets. */
.menu__btn {
  display: none;
}

.menu__btn[aria-expanded='true'] {
  color: var(--text);
  background: var(--surface-alt);
}

/* Both glyphs share the one cell and the switch is a swap of presence — the
   leaving icon turns and shrinks out as the arriving one turns in — rather
   than a `display` cut. The theme itself eases over 280ms (see `.theme-easing`
   below); an icon that hard-cut while everything around it eased was the one
   thing on the page that changed like a computer. */
.themetoggle svg {
  grid-area: 1 / 1;
  transition: opacity var(--d-base) ease, transform var(--d-base) var(--ease-out);
}

.themetoggle .i-moon {
  opacity: 0;
  transform: rotate(-40deg) scale(0.6);
}

:root[data-theme='light'] .themetoggle .i-moon {
  opacity: 1;
  transform: none;
}

:root[data-theme='light'] .themetoggle .i-sun {
  opacity: 0;
  transform: rotate(40deg) scale(0.6);
}

/* --- Sections, on a phone: the panel under the bar ---------------------------
 *
 * Below 900px the five section links no longer fit in the bar. They were a
 * dropdown hanging off the trailing button — a desktop idiom, five bare words
 * on a white card — and then a sheet rising from the bottom, which the owner
 * did not want. This is the third form and the one that stays: the bar stays
 * exactly where it is, its button becomes a close, and the PAGE below it gives
 * way to a full-height glass panel carrying the sections as large rows, each
 * saying where it goes and what is there. It is how apple.com answers the same
 * question on a phone, and it is what Safari's own menus do: the chrome holds,
 * the content changes.
 *
 * A popover (`popover` + `popovertarget`), so the browser owns what a script
 * used to: the top layer above every stacking context — a fixed element inside
 * the bar could not reach it, because `backdrop-filter` makes the bar its
 * containing block — Escape to close, a tap outside to close, focus handed
 * back. No scrim: the panel IS the page while it is up, and the bar above it is
 * left un-dimmed so its close button reads as live. The script's only part is
 * courtesy: closing it when a row is chosen, holding the page still behind it,
 * and turning the bar's glyph.
 */
.sheet {
  /* Reset the popover's own box: it centres itself with auto margins and a
     2px border by default. */
  position: fixed;
  inset: var(--nav-h) 0 0 0;
  margin: 0;
  border: 0;
  width: auto;
  height: auto;
  max-width: none;
  max-height: none;
  padding: var(--s-xl) var(--s-2xl) calc(var(--s-3xl) + env(safe-area-inset-bottom, 0px));
  overflow: auto;
  background: var(--glass-panel-strong);
  /* Closed, the glass is clear; the blur arrives with the panel and leaves
     with it, so the material is seen to form rather than to fade in. */
  backdrop-filter: blur(0px) saturate(100%);
  -webkit-backdrop-filter: blur(0px) saturate(100%);
  border-top: 1px solid var(--border);
  color: var(--text);
  /* It arrives as the bar's own material arriving: a short drop from under the
     bar on the app's curve over `slow`, and leaves the same way reversed.
     `display` and `overlay` transition discretely so it stays in the top layer
     for the length of its exit rather than vanishing on the first frame. */
  opacity: 0;
  transform: translateY(-8px);
  transition:
    opacity var(--d-slow) ease,
    transform var(--d-slow) var(--ease-in),
    backdrop-filter var(--d-slow) ease,
    -webkit-backdrop-filter var(--d-slow) ease,
    display var(--d-slow) allow-discrete,
    overlay var(--d-slow) allow-discrete;
}

/* Arriving, the panel decelerates into place; leaving (the rule above), it
   accelerates away on the mirror of that curve. */
.sheet:popover-open {
  transition:
    opacity var(--d-slow) ease,
    transform var(--d-slow) var(--ease-out),
    backdrop-filter var(--d-slow) ease,
    -webkit-backdrop-filter var(--d-slow) ease,
    display var(--d-slow) allow-discrete,
    overlay var(--d-slow) allow-discrete;
  opacity: 1;
  transform: none;
  backdrop-filter: blur(24px) saturate(160%);
  -webkit-backdrop-filter: blur(24px) saturate(160%);
}

@starting-style {
  .sheet:popover-open {
    opacity: 0;
    transform: translateY(-8px);
    backdrop-filter: blur(0px) saturate(100%);
    -webkit-backdrop-filter: blur(0px) saturate(100%);
  }
}

/* No scrim — see above. The backdrop still exists, and it is what catches the
   tap outside; it just paints nothing. */
.sheet::backdrop {
  background: transparent;
}

@supports not (backdrop-filter: blur(1px)) {
  .sheet {
    background: var(--bg-elevated);
  }
}

/* One column at a reading measure, centred, whatever the panel's width. */
.sheet__inner {
  max-width: 560px;
  margin: 0 auto;
}

.sheet__eyebrow {
  margin-bottom: var(--s-sm);
}

.sheet__list {
  display: grid;
}

/* The rows RISE IN, one after another — the app's list entrance: 8px over
   `base` on EASE_OUT, 45ms apart, the first one with the panel itself. The
   panel opening is what starts them; the index each row carries is what
   staggers them. */
.sheet__list li {
  border-top: 1px solid var(--border);
}

.sheet__list li:last-child {
  border-bottom: 1px solid var(--border);
}

.sheet:popover-open .sheet__list li,
.sheet.is-open .sheet__list li {
  animation: entrance var(--d-base) var(--ease-out) both;
  animation-delay: calc(var(--stagger) * var(--i, 0));
}

/* A row: where, and what is there. Large — this panel is the page while it is
   up, and a table of contents at 17px in a 375pt screen's worth of space is a
   list of small print. The name is the thing; the line under it says what a
   tap gets. Hover and the current section both light the NAME only. */
/* Square, not rounded: the rows are divided by full-width hairlines, and a
   rounded fill between two straight rules reads as a card that has slipped. */
.sheet__list a {
  display: block;
  padding: var(--s-lg) var(--s-xs);
  transition: background-color var(--d-fast) ease;
}

/* A wash, not a surface: the name already turns blue under the pointer, and a
   full `surfaceAlt` slab behind a 24px word was two answers to one question.
   The press is a step firmer, so the finger still gets its reply. */
@media (hover: hover) {
  .sheet__list a:hover {
    background: var(--wash);
  }
}

.sheet__list a:active {
  background: var(--surface-alt);
  transition: none;
}

.sheet__name {
  display: block;
  font-size: 1.5rem;
  line-height: 1.15;
  font-weight: 600;
  letter-spacing: var(--tr-tight);
  color: var(--text);
  transition: color var(--d-fast) ease;
}

.sheet__list a[aria-current='location'] .sheet__name {
  color: var(--primary);
}

@media (hover: hover) {
  .sheet__list a:hover .sheet__name {
    color: var(--primary);
  }
}

.sheet__desc {
  display: block;
  margin-top: 4px;
  font-size: 0.9375rem;
  line-height: 1.45;
  color: var(--text-2);
}

.sheet__foot {
  margin-top: var(--s-2xl);
  display: grid;
  gap: var(--s-md);
}

/* The two documents, as one quiet line; the middot is punctuation, not a word. */
/* The separator is the app's own — `healthDot`, a 3px disc in `borderStrong`
   — not a middot character: a glyph sits wherever its font puts it, which was
   above the words' middle, while a disc centred by the row is centred. */
.sheet__minor {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-sm);
  font-size: 0.8125rem;
  color: var(--text-2);
}

.sheet__dot {
  width: 3px;
  height: 3px;
  border-radius: 2px;
  background: var(--border-strong);
}

/* 44pt tall as well: 13px type at 1.55 is 20px, and 0.75rem each side makes
   the row a target rather than a word. */
.sheet__minor a {
  padding: 0.75rem var(--s-sm);
  border-radius: var(--r-sm);
  color: var(--text-2);
}

.sheet__minor a:active {
  color: var(--text);
}

@media (hover: hover) {
  .sheet__minor a:hover {
    color: var(--text);
  }
}

/* The bar's button turns: the list glyph leaves and the cross arrives, the
   same swap of presence the theme toggle makes. The attribute is set by the
   script from the popover's own state. */
.menu__btn svg {
  grid-area: 1 / 1;
  transition: opacity var(--d-base) ease, transform var(--d-base) var(--ease-out);
}

.menu__btn .i-close {
  opacity: 0;
  transform: rotate(-40deg) scale(0.6);
}

.menu__btn[aria-expanded='true'] .i-close {
  opacity: 1;
  transform: none;
}

.menu__btn[aria-expanded='true'] .i-list {
  opacity: 0;
  transform: rotate(40deg) scale(0.6);
}

/* Without the Popover API the panel is a plain block the script shows and
   hides; it keeps its shape and loses the light dismiss. */
.sheet--fallback {
  display: none;
  z-index: 60;
  opacity: 1;
  transform: none;
}

.sheet--fallback.is-open {
  display: block;
}

/* The bar's call to action has a shorter name on the narrowest phones, where
   the long one plus two 44px buttons no longer fits beside the brand. It is
   the section's own name — the band it points at is headed "Early access" —
   rather than a new verb, so the destination still matches the label. */
.btn__short {
  display: none;
}

@media (max-width: 900px) {
  .nav__links {
    display: none;
  }
  .nav__actions {
    margin-left: auto;
  }
  .menu__btn {
    display: grid;
  }
}

@media (max-width: 480px) {
  .btn__long {
    display: none;
  }
  .btn__short {
    display: inline;
  }
}

/* --- Section scaffolding ------------------------------------------------- */

.section {
  padding: clamp(64px, 9vw, 116px) 0;
}

.section--tight {
  padding: clamp(48px, 6vw, 76px) 0;
}

.section__head {
  max-width: 680px;
  margin-bottom: clamp(2rem, 4vw, 52px);
}

.section__head .caps {
  margin-bottom: var(--s-md);
}

.section__head p {
  margin-top: var(--s-lg);
}

.rule {
  height: 1px;
  background: var(--border);
  border: 0;
  margin: 0;
}

/* --- Hero --------------------------------------------------------------- */

/*
 * ON THE CANVAS, BECAUSE THE APP NO LONGER HAS A PLANE.
 *
 * This was a flat band of `--hero` with the rest of the page as a rounded sheet
 * lifted over its bottom edge — the composition of the old live logger. The
 * 2026-10 redesign retired that plane on every screen: Home is a greeting, the
 * day's title set large and the figures on a recessed tray, all on the canvas,
 * and the logger is the same. So the page's first screen is the canvas too, and
 * the sections below simply continue on it, divided by hairlines.
 */
.hero {
  position: relative;
  background: var(--bg);
  padding-top: calc(var(--nav-h) + clamp(2.75rem, 6vw, 84px));
  padding-bottom: clamp(52px, 6vw, 88px);
}

/* Kept as a wrapper — the sections after the hero — with nothing of its own to
   draw now that there is no plane to ride over. */
.sheet-page {
  position: relative;
  background: var(--bg);
  border-top: 1px solid var(--border);
}

/* A secondary action beside the primary capsule is the design's round button
   material: the card colour with a hairline, standing on the canvas. */
.hero .btn--secondary {
  background: var(--surface);
  border-color: var(--border);
}

@media (hover: hover) {
  .hero .btn--secondary:hover {
    background: var(--surface-pressed);
  }
}

.hero__grid {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: clamp(2rem, 5vw, 72px);
  align-items: center;
}

.hero__copy {
  max-width: 600px;
}

.hero h1 {
  margin-top: var(--s-lg);
}

.hero h1 em {
  font-style: normal;
  color: var(--primary);
}

.hero .lede {
  margin-top: var(--s-2xl);
  max-width: 520px;
}

.hero__cta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-md);
  margin-top: var(--s-3xl);
}

/*
 * The first moment of motion on the page: the hero arrives.
 *
 * This replaced a JavaScript reveal that set `opacity: 0` in CSS and cleared it
 * from a script, so with scripts off everything below the fold was simply not
 * there. Everything that moves on this page is CSS now — a load animation here,
 * scroll-driven ones further down — and none of it can hide content.
 *
 * The curve is the app's EASE_OUT: it decelerates into place with no overshoot,
 * which is right for something that was not thrown. The durations are longer
 * than the app's `slow`, deliberately — a section landing in a 900px viewport
 * is not a sheet landing on a 393pt screen, and the same 320ms reads as a
 * blink at this size. Each line of the copy follows the last by 70ms; the
 * phone, heavier, comes last and slower, and then its own feed rises in.
 */
@keyframes arrive {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.hero__copy > * {
  animation: arrive 0.55s var(--ease-out) both;
}

.hero__copy > :nth-child(2) {
  animation-delay: 0.07s;
}
.hero__copy > :nth-child(3) {
  animation-delay: 0.14s;
}
.hero__copy > :nth-child(4) {
  animation-delay: 0.21s;
}

.hero__art {
  animation: arrive 0.7s var(--ease-out) 0.18s both;
}

/*
 * The feed rises in after the phone has landed — `useEntranceStyle`, exactly:
 * an 8px rise over `DURATION.base` on EASE_OUT, each card 45ms after the one
 * above it. In the app it is the cards alone that do this; the hero card and
 * the switcher are simply there, so they are simply there here too.
 */
@keyframes entrance {
  from {
    opacity: 0;
    transform: translateY(var(--rise));
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.ios--home .wcard {
  animation: entrance var(--d-base) var(--ease-out) both;
  animation-delay: calc(0.62s + var(--stagger) * var(--i, 0));
}

@media (max-width: 980px) {
  .hero__grid {
    grid-template-columns: minmax(0, 1fr);
  }
  .hero__art {
    justify-self: center;
  }
}

/* ==========================================================================
   The phone — a real render of the app's screens, not a screenshot.

   `.ios` is a 393 × 852pt canvas, an iPhone 16's, and everything inside it is
   written in the app's own POINTS: `spacing.lg` is 16px here because it is
   16pt there, `fontSize.xs` is 11px, a set row is 46px tall, `tracking.caps`
   is 1.6px. One `zoom` on the canvas then fits it into the frame — so every
   proportion, weight and colour is the app's, and only the overall size is the
   page's. The three screens are `app/(tabs)/index.tsx`, `app/workout/active.tsx`
   and `app/(tabs)/calories.tsx`, rebuilt rule for rule from their stylesheets
   and the components they compose; the tab bar is the iOS 26 system bar the
   app asks for through `NativeTabs`, with the SF Symbols it names.

   When a screen changes in the app, the block below it here is the copy that
   has to change with it. Nothing imports it; the README says so.
   ========================================================================== */

.phone {
  /* The frame's inner width, and the zoom that puts 393pt into it. Both
     change together at the narrow breakpoint at the foot of the sheet. */
  --scr-w: 330px;
  --k: 0.8397;
  /* How many times its size the phone is drawn: 1, except a 3D phone on a screen under 2x,
     which is drawn at twice its size and shrunk back in 3D (see `.dev .phone`). */
  --hd: 1;
  position: relative;
  width: calc((var(--scr-w) + 20px) * var(--hd));
  height: calc((var(--scr-w) * 852 / 393 + 20px) * var(--hd));
  flex: 0 0 auto;
  border-radius: calc(54px * var(--hd));
  padding: calc(10px * var(--hd));
  background: var(--phone-bezel);
  border: calc(1px * var(--hd)) solid var(--phone-rim);
  box-shadow: var(--shadow-float);
}

.phone__screen {
  position: relative;
  width: calc(var(--scr-w) * var(--hd));
  height: calc(var(--scr-w) * 852 / 393 * var(--hd));
  border-radius: calc(44px * var(--hd));
  overflow: hidden;
  background: var(--bg);
}

.ios {
  position: relative;
  width: 393px;
  height: 852px;
  zoom: calc(var(--k) * var(--hd));
  overflow: hidden;
  background: var(--bg);
  color: var(--text);
  font-size: 15px;
  line-height: 1.2;
}

/* `zoom` lays the canvas out at the scaled size, so text is rasterised at the
   size it is seen. An engine without it gets the same picture through a
   transform — the 393pt box stays 393pt in layout, scales about its top-left
   corner, and the frame's `overflow: hidden` clips exactly at its edge. */
@supports not (zoom: 1) {
  .ios {
    transform: scale(calc(var(--k) * var(--hd)));
    transform-origin: top left;
  }
}

/* The Dynamic Island: a cutout, drawn in hardware black over every theme. */
.ios::before {
  content: '';
  position: absolute;
  top: 11px;
  left: 50%;
  width: 126px;
  height: 37px;
  transform: translateX(-50%);
  border-radius: var(--r-pill);
  background: var(--island);
  z-index: 7;
}

/* The scroll edge under the system tab bar. iOS dissolves content into the
   canvas where it passes beneath the bar rather than letting it run to the
   edge of the screen. The canvas colour, so it is invisible in itself and only
   the rows entering it fade. */
.ios__fade {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 130px;
  background: linear-gradient(to bottom, transparent, var(--bg) 70%);
  pointer-events: none;
  z-index: 1;
}

/* The home indicator. */
.hi {
  position: absolute;
  bottom: 8px;
  left: 50%;
  width: 139px;
  height: 5px;
  margin-left: -69.5px;
  border-radius: 3px;
  background: var(--text);
  z-index: 5;
}

/* `monoFont` — Geist Mono, for the micro-caps and the tabular columns. */
.mono {
  font-family: var(--font-mono);
}

/* --- Status bar, either side of the island ------------------------------- */

.sb {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 59px;
  z-index: 6;
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--text);
}

.sb__time {
  position: absolute;
  top: 18px;
  left: 0;
  width: 134px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.sb__right {
  position: absolute;
  top: 21px;
  right: 0;
  width: 134px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
}

.sb__bars {
  display: flex;
  align-items: flex-end;
  gap: 1.5px;
  height: 11px;
}

.sb__bars i {
  width: 3px;
  background: currentColor;
  border-radius: 1px;
}

.sb__bars i:nth-child(1) {
  height: 4px;
}
.sb__bars i:nth-child(2) {
  height: 6px;
}
.sb__bars i:nth-child(3) {
  height: 8.5px;
}
.sb__bars i:nth-child(4) {
  height: 11px;
}

.sb__batt {
  width: 27px;
  height: 13px;
  border: 1px solid currentColor;
  border-radius: 4px;
  padding: 1.5px;
  opacity: 0.9;
}

.sb__batt span {
  display: block;
  height: 100%;
  width: 80%;
  background: currentColor;
  border-radius: 2px;
}

/* --- The system tab bar, iOS 26 ------------------------------------------- */

/* `NativeTabs`: a floating glass capsule with four labelled items, the
   selected one under a lens in the bar's own material and tinted `primary`.
   There is no fifth tab and no start button — the card on Home is the way in. */
.tb {
  position: absolute;
  left: 16px;
  right: 16px;
  bottom: 44px;
  height: 62px;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-around;
  padding: 0 6px;
  border-radius: var(--r-pill);
  background: var(--glass-bar);
  backdrop-filter: blur(24px) saturate(160%);
  -webkit-backdrop-filter: blur(24px) saturate(160%);
  border: 1px solid var(--glass-rim);
  box-shadow: var(--shadow-floating);
}

.tb__item {
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 3px;
  width: 80px;
  height: 52px;
  border-radius: var(--r-pill);
  color: var(--text-3);
  font-size: 10px;
  font-weight: 500;
}

.tb__item svg {
  width: 24px;
  height: 24px;
}

.tb__item.on {
  color: var(--primary);
  background: var(--tab-lens);
}

/* --- Controls the three screens share ------------------------------------- */

/* `Button` — `radius.md`, a hairline reserved on every variant, three sizes. */
.ibtn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 44px;
  padding: 0 16px;
  border-radius: 10px;
  border: 1px solid transparent;
  font-size: 15px;
  font-weight: 600;
  white-space: nowrap;
}

.ibtn--sm {
  min-height: 36px;
  padding: 0 12px;
  font-size: 13px;
}

.ibtn--lg {
  min-height: 52px;
  padding: 0 20px;
  font-size: 17px;
}

.ibtn--primary {
  background: var(--primary);
  border-color: var(--primary-rim);
  color: var(--on-filled);
}

.ibtn--secondary {
  background: var(--surface-alt);
  border-color: var(--border);
  color: var(--text);
}

.ibtn--full {
  width: 100%;
}

/* `shape="capsule"` — the redesign's forward actions. */
.ibtn--cap {
  border-radius: var(--r-pill);
}

/* `HERO_BUTTON`: the wide pill and the round button beside it, 58pt, with the
   label at 17 and the glyph trailing. */
.ibtn--hero {
  min-height: 58px;
  padding: 0 22px;
  font-size: 17px;
}

/* `SegmentedControl` after the redesign: a pill-shaped tray with a hairline,
   and the chosen segment a pill of the CARD colour sliding inside it, with
   `text` on it. The accent is for actions, not for "which of these is
   chosen", so nothing here is blue. */
.seg {
  display: flex;
  gap: 3px;
  padding: 3px;
  border-radius: var(--r-pill);
  border: 0.5px solid var(--border);
  background: var(--surface-alt);
}

.seg span {
  flex: 1 1 0;
  min-height: 38px;
  display: grid;
  place-items: center;
  padding: 0 8px;
  border-radius: var(--r-pill);
  font-size: 13px;
  font-weight: 500;
  color: var(--text-2);
  white-space: nowrap;
}

.seg span.on {
  color: var(--text);
  font-weight: 600;
  border: 0.5px solid var(--border);
  background: var(--surface);
}

/* `FillTrack` — a rounded track in `border`, filled to a ratio. */
.track {
  height: 4px;
  border-radius: 2px;
  background: var(--border);
  overflow: hidden;
}

.track > i {
  display: block;
  height: 100%;
  border-radius: 2px;
}

/* --- Home — `app/(tabs)/index.tsx` ---------------------------------------- */

/* The redesign's Home, `HomeHero` then the feed: one line of greeting with join,
   search and the avatar as small round buttons; TODAY — the routine's name set
   large, a wide Start capsule and a round button the same height beside it;
   the range's figures on the recessed tray; the pill switcher; the history as
   a column of cards. Text sits 24pt in, cards 16pt — the design's two gutters.
   `Screen edges={TOP_EDGE}` is the 59px of padding. */
.hm {
  position: absolute;
  inset: 0;
  padding-top: 59px;
  overflow: hidden;
}

.hm__greet-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 16px 0 24px;
}

.hm__greeting {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--text-2);
  font-size: 15px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.hm__greeting b {
  color: var(--text);
  font-weight: 600;
}

/* `DISC`: 36pt, the card colour with a hairline. */
.hm__disc {
  width: 36px;
  height: 36px;
  flex: 0 0 auto;
  border-radius: var(--r-pill);
  display: grid;
  place-items: center;
  border: 0.5px solid var(--border);
  background: var(--surface);
  color: var(--text-2);
}

.hm__avatar {
  width: 36px;
  height: 36px;
  flex: 0 0 auto;
  border-radius: var(--r-pill);
  display: grid;
  place-items: center;
  background: var(--primary-soft);
  color: var(--primary);
  font-size: 14px;
  font-weight: 700;
}

.hm__today {
  padding: 28px 24px 0;
}

.hm__eyebrow {
  color: var(--primary);
  font-size: 11px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
}

/* A routine's NAME, so the sans — 34 bold, the iOS large title. The serif is
   kept for the fixed words that title a screen. */
.hm__title {
  margin-top: 8px;
  font-size: 34px;
  line-height: 38px;
  font-weight: 700;
  letter-spacing: -0.8px;
}

/* As wide as the tray under it: pulled out of the 24pt text gutter to the
   16pt card one ("the two buttons has the same width as the modal under it"). */
.hm__actions {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 20px -8px 0;
}

.hm__actions .ibtn--hero {
  flex: 1 1 0;
}

.hm__round {
  width: 58px;
  height: 58px;
  flex: 0 0 auto;
  border-radius: var(--r-pill);
  display: grid;
  place-items: center;
  border: 0.5px solid var(--border);
  background: var(--surface);
  color: var(--text);
}

/* The tray: `surfaceAlt`, `radius.xxl`, a hairline. */
.hm__tray {
  margin: 26px 16px 0;
  padding: 16px 20px;
  border-radius: var(--r-xxl);
  border: 0.5px solid var(--border);
  background: var(--surface-alt);
}

.hm__vol-label {
  color: var(--text-2);
  font-size: 11px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
}

.hm__vol-row {
  display: flex;
  align-items: baseline;
  gap: 7px;
  margin-top: 6px;
}

.hm__vol {
  font-size: 36px;
  line-height: 1.1;
  font-weight: 700;
  letter-spacing: -1.4px;
  font-variant-numeric: tabular-nums;
}

.hm__vol-unit {
  color: var(--text-2);
  font-size: 15px;
  font-weight: 600;
}

.hm__delta {
  color: var(--primary);
  font-size: 13px;
  font-weight: 600;
}

.hm__stats {
  display: flex;
  align-items: center;
  gap: 18px;
  margin-top: 14px;
}

.hm__stat-v {
  font-size: 20px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* The one figure on the tray that earns the accent: a streak is what a lifter
   is actually protecting week to week. */
.hm__stat-v--accent {
  color: var(--primary);
}

.hm__stat-l {
  margin-top: 3px;
  color: var(--text-2);
  font-size: 11px;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  white-space: nowrap;
}

.hm__div {
  width: 0.5px;
  height: 26px;
  background: var(--border-strong);
}

.hm__filter {
  padding: 20px 16px 0;
}

.hm__heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 20px 24px 12px;
}

.hm__heading-t {
  font-size: 17px;
  font-weight: 600;
}

.hm__heading-d {
  color: var(--text-2);
  font-size: 13px;
}

/* `WorkoutCard`: a card with a hairline, its own date and clock on one mono
   line at the top, the name and a counted PR pill, the volume to the right. */
.wcard {
  margin: 0 16px 10px;
  padding: 14px 16px 15px;
  border-radius: var(--r-xxl);
  border: 0.5px solid var(--border);
  background: var(--surface);
}

.wcard__meta {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--text-3);
  font-size: 11px;
  white-space: nowrap;
  overflow: hidden;
}

.wcard__day {
  flex: 0 0 auto;
  letter-spacing: 1.6px;
  text-transform: uppercase;
}

.wcard__day--today {
  color: var(--primary);
}

.wcard__body {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin-top: 8px;
}

.wcard__main {
  flex: 1 1 auto;
  min-width: 0;
}

.wcard__title-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.wcard__title {
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.wcard__pr {
  flex: 0 0 auto;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  background: var(--gold-soft);
  color: var(--gold);
  font-size: 11px;
  font-weight: 600;
}

.wcx {
  display: grid;
  gap: 6px;
  margin-top: 8px;
}

.wcx__line {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

/* `ExerciseThumb`: a rounded square — circles are for people here, rounded
   squares for things — its corner a quarter of its side. */
.wcx__thumb {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  border-radius: 8px;
  background: var(--demo-plate);
  object-fit: cover;
}

.wcx__thumb--initials {
  display: grid;
  place-items: center;
  border: 0.5px solid rgb(var(--m) / 0.45);
  background: rgb(var(--m) / 0.18);
  color: rgb(var(--m));
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.3px;
  /* `capCentred`: Familjen's capitals sit 0.072em low in a box centred by
     its line, so the content box is raised by that much — twice it as padding
     below — and the letters land on the plate's optical centre. */
  padding-bottom: 0.144em;
}

.wcx__name {
  min-width: 0;
  color: var(--text-2);
  font-size: 13px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.wcx__more {
  margin: 2px 0 0 40px;
  color: var(--primary);
  font-size: 13px;
  font-weight: 600;
}

.wcard__trail {
  flex: 0 0 auto;
  text-align: right;
}

.wcard__hero {
  font-size: 20px;
  font-weight: 700;
  letter-spacing: -0.4px;
  font-variant-numeric: tabular-nums;
}

.wcard__unit {
  margin-top: 2px;
  color: var(--text-3);
  font-size: 11px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
}

/* --- The live logger — `app/workout/active.tsx` --------------------------- */

/* After the redesign the logger is on the canvas like every other screen — the
   blue plane it was the last keeper of is gone. Drawn scrolled down to the
   exercises, which is where a session is spent: the sticky bar is up (the
   minimise disc, the clock, Invite and Finish — the whole right-hand group, so
   nothing needs a scroll back to the top), and the first exercise card is
   whole under it. */
.ios--logger {
  background: var(--bg);
}

.lg {
  position: absolute;
  inset: 0;
  padding-top: 59px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

/* 60pt: 4 above the 44pt controls and 12 below, because the sets scroll under
   its hairline and 4 there read as the buttons touching them. */
.lg__bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 16px 12px;
  border-bottom: 0.5px solid var(--border);
  background: var(--bg);
}

/* `GlassIconButton` and `GlassButton` — 44pt of Liquid Glass, a disc and a
   capsule, drawn here as the lens reads on an iOS 26 phone: the pill body over
   whatever is under it, with its rim. */
.gdisc,
.gpill {
  flex: 0 0 auto;
  min-height: 44px;
  border-radius: var(--r-pill);
  border: 0.5px solid var(--pill-rim);
  background: var(--glass-pill);
  backdrop-filter: blur(16px) saturate(160%);
  -webkit-backdrop-filter: blur(16px) saturate(160%);
  color: var(--text);
}

.gdisc {
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
}

.gpill {
  display: flex;
  align-items: center;
  padding: 0 16px;
  font-size: 15px;
  font-weight: 600;
}

/* `stickyClock`: beside the chevron, held there by an auto margin rather than
   floated mid-bar by `space-between`. */
.lg__clock {
  margin: 0 auto 0 4px;
  font-size: 17px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.lg__right {
  display: flex;
  align-items: center;
  gap: 8px;
}

.lg__section {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 18px 24px 10px;
}

.lg__section-l {
  font-size: 17px;
  font-weight: 600;
}

.lg__section-c {
  color: var(--text-2);
  font-size: 13px;
}

.lg__list {
  display: grid;
  gap: 8px;
  padding: 0 16px;
}

/* `ExerciseBlock`: a card — `radius.xxl`, a hairline — holding the picture, the
   name in the accent, the rest chip, the column heads and the set rows. */
.exb {
  display: grid;
  gap: 8px;
  padding: 12px;
  border-radius: var(--r-xxl);
  border: 0.5px solid var(--border);
  background: var(--surface);
}

.exb__head {
  display: flex;
  align-items: center;
  gap: 8px;
}

.exb__main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 4px 0;
}

/* `ExerciseThumb` at 44, the picture every list draws — the demo still, or the
   initials on their muscle's tint. */
.exb__thumb {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  border-radius: 11px;
  background: var(--demo-plate);
  object-fit: cover;
}

.exb__thumb--initials {
  display: grid;
  place-items: center;
  padding-bottom: 0.144em;
  border: 0.5px solid rgb(var(--m) / 0.45);
  background: rgb(var(--m) / 0.18);
  color: rgb(var(--m));
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.3px;
}

.exb__text {
  min-width: 0;
  display: grid;
  gap: 1px;
}

.exb__name {
  color: var(--primary);
  font-size: 15px;
  font-weight: 600;
}

.exb__sub {
  color: var(--text-3);
  font-size: 11px;
}

.exb__more {
  width: 40px;
  height: 40px;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  color: var(--text-3);
}

/* The design's rest chip: a tray capsule under the exercise. */
.exb__rest {
  justify-self: start;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 32px;
  padding: 0 12px;
  border-radius: var(--r-pill);
  background: var(--surface-alt);
  color: var(--text-2);
  font-size: 13px;
  font-weight: 600;
}

.exb__cols {
  display: flex;
  align-items: center;
  padding: 4px 4px 2px;
  color: var(--text-3);
  font-size: 11px;
  letter-spacing: 1px;
  text-transform: uppercase;
}

/* `setColumnStyles`: the number, last time's set, one cell per figure, the tick. */
.c-set {
  width: 38px;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
}

.c-prev {
  flex: 1.7 1 0;
  min-width: 0;
  padding: 0 4px;
  text-align: center;
}

.c-in {
  flex: 1 1 0;
  min-width: 0;
  padding: 0 2px;
  text-align: center;
}

.c-chk {
  width: 44px;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
}

/* The design's row: a 14pt corner and the card's own colour until it is logged;
   a ticked set takes the accent's soft tint — the accent, not green. */
.srow {
  display: flex;
  align-items: center;
  min-height: 46px;
  margin-bottom: 4px;
  padding: 3px 4px;
  border-radius: var(--r-lg);
  background: var(--surface);
}

.srow--done {
  background: var(--primary-soft);
}

/* A 28pt disc: a working set's number on the tray, a lettered set in its own
   colour on its own soft tint — the warm-up's W gold on gold. */
.srow__disc {
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  padding-bottom: 0.144em;
  border-radius: var(--r-pill);
  background: var(--surface-alt);
  color: var(--text-2);
  font-size: 13px;
  font-style: normal;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.srow__disc--w {
  background: var(--gold-soft);
  color: var(--gold);
}

.srow .c-prev {
  color: var(--text-2);
  font-size: 13px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* A record takes over the previous column: until the set is logged last week's
   numbers are the target; once it is a record they are what was just beaten. */
.srow .c-prev--pr {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 3px;
  color: var(--gold);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.3px;
}

/* A set still to do sits in tray boxes; once it is logged the box goes and the
   row's tint is the frame. */
.srow .c-in b {
  display: grid;
  place-items: center;
  min-height: 40px;
  border-radius: var(--r-md);
  background: var(--surface-alt);
  font-size: 15px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.srow--done .c-in b {
  background: transparent;
}

/* Last time's figure, carried down as the placeholder. */
.srow .c-in b.srow__hint {
  color: var(--text-3);
}

.srow__chk {
  width: 36px;
  height: 36px;
  display: grid;
  place-items: center;
  border-radius: var(--r-md);
  background: var(--surface-alt);
  color: var(--text-3);
}

.srow--done .srow__chk {
  background: var(--primary);
  color: var(--on-filled);
}

/* Add Set alone is the full width; it gives up half when "same again" arrives. */
.exb__foot {
  display: flex;
  gap: 8px;
  margin-top: 4px;
}

.exb__add {
  flex: 1 1 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: 44px;
  border-radius: var(--r-pill);
  background: var(--surface-alt);
  font-size: 15px;
  font-weight: 600;
}

.exb__add svg {
  color: var(--primary);
}

.exb__again {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-height: 44px;
  padding: 0 12px;
  border-radius: var(--r-pill);
  background: var(--primary-soft);
  color: var(--primary);
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.exb__again svg {
  flex: 0 0 auto;
}

.exb__again span {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* `RestTimer` — a `glassPanelStrong` body at `radius.lg`, 12px in from the
   sides and 12px above the safe area, with the app's own floating shadow. */
.rest {
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 46px;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border-radius: 14px;
  background: var(--glass-panel-strong);
  backdrop-filter: blur(24px) saturate(160%);
  -webkit-backdrop-filter: blur(24px) saturate(160%);
  border: 1px solid var(--glass-rim);
  box-shadow: var(--shadow-floating);
}

.rest__ring {
  width: 42px;
  height: 42px;
  flex: 0 0 auto;
}

/* The ring is written once a second and sweeps between writes, so the arc
   moves continuously for the cost of one DOM update per tick. `linear`, not
   an ease: a clock does not slow down as it approaches the next second. */
.rest__ring [data-rest-arc] {
  transition: stroke-dashoffset 1s linear;
}

.rest__readout {
  flex: 1 1 auto;
  min-width: 0;
}

.rest__label {
  color: var(--text-3);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 1px;
}

.rest__time {
  font-size: 17px;
  font-weight: 700;
}

.rest__adj {
  display: flex;
  align-items: center;
  gap: 1px;
  min-height: 36px;
  padding: 0 8px;
  border-radius: var(--r-pill);
  background: var(--surface-alt);
  color: var(--text);
  font-size: 13px;
  font-weight: 600;
}

.rest__skip {
  display: flex;
  align-items: center;
  gap: 4px;
  min-height: 36px;
  padding: 0 12px;
  border-radius: var(--r-pill);
  background: var(--primary-soft);
  color: var(--primary);
  font-size: 13px;
  font-weight: 600;
}

/* --- Nutrition — `app/(tabs)/calories.tsx` -------------------------------- */

/* After the redesign there is no title row: the header IS the day — two round
   buttons either side of "Today" and the date — with the Diary / Recipes switch
   under it, and the pager's dots below that. The day's figure is type on the
   canvas, what is left to eat at 44 bold with Goal beside it and a bar under it;
   then Add food as the wide capsule, the macros on the tray, and the meals in
   one card. */
.nu {
  position: absolute;
  inset: 0;
  padding-top: 59px;
  overflow: hidden;
}

.nu__header {
  padding-bottom: 4px;
}

.nu__daynav {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 16px 0;
}

.nu__step {
  width: 36px;
  height: 36px;
  flex: 0 0 auto;
  border-radius: var(--r-pill);
  display: grid;
  place-items: center;
  border: 0.5px solid var(--border);
  background: var(--surface);
  color: var(--text-2);
}

.nu__step--off {
  opacity: 0.45;
}

.nu__day {
  flex: 1 1 0;
  min-width: 0;
  display: grid;
  justify-items: center;
  padding: 2px 0;
}

.nu__day-t {
  font-size: 15px;
  font-weight: 600;
}

.nu__day-s {
  margin-top: 1px;
  color: var(--text-2);
  font-size: 13px;
}

.nu__switch {
  margin: 14px 16px 0;
}

/* The pager's dots, above the pages: the accent for the one you are on. */
.nu__dots {
  display: flex;
  justify-content: center;
  padding: 4px 0 4px;
}

.nu__dot {
  width: 6px;
  height: 6px;
  margin: 4px;
  border-radius: 3px;
  background: var(--border-strong);
}

.nu__dot--on {
  background: var(--primary);
}

.nu__page {
  display: grid;
}

.nu__hero {
  padding: 16px 24px 0;
}

.nu__eyebrow {
  color: var(--primary);
  font-size: 11px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
}

.nu__figure-row {
  display: flex;
  align-items: flex-end;
  gap: 10px;
  margin-top: 8px;
}

/* A figure, so the sans — 44 bold, like Home's volume. */
.nu__figure {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 44px;
  line-height: 48px;
  font-weight: 700;
  letter-spacing: -1.4px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.nu__figure small {
  color: var(--text-2);
  font-size: 18px;
  font-weight: 600;
  letter-spacing: 0;
}

.nu__goal {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  min-height: 34px;
  margin-bottom: 6px;
  padding: 0 14px;
  border-radius: var(--r-pill);
  background: var(--primary-soft);
  color: var(--primary);
  font-size: 13px;
  font-weight: 600;
}

.nu__bar {
  margin: 16px 24px 0;
}

.track.nu__track {
  height: 8px;
  border-radius: 4px;
  border: 0.5px solid var(--border);
  background: var(--surface-alt);
}

.nu__track > i {
  border-radius: 4px;
}

.nu__meta {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  margin-top: 8px;
  color: var(--text-2);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.nu__meta b {
  color: var(--text);
  font-weight: 600;
}

/* As wide as the tray under it, like every row of buttons over a card. */
.nu__add {
  margin: 20px 16px 0;
}

.nu__tray {
  margin: 14px 16px 0;
  padding: 16px 20px;
  border-radius: var(--r-xxl);
  border: 0.5px solid var(--border);
  background: var(--surface-alt);
}

/* `MacroTiles` — three columns, the label in the mono, a 5px track in the
   macro's own colour (the bars keep their hues against the design on purpose). */
.nu__tiles {
  display: flex;
  gap: 16px;
}

.nu__tile {
  flex: 1 1 0;
  min-width: 0;
}

.nu__tile-l {
  color: var(--text-2);
  font-size: 11px;
  letter-spacing: 1px;
  text-transform: uppercase;
}

.nu__tile-v {
  margin-top: 5px;
  font-size: 16px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.nu__tile-t {
  color: var(--text-2);
  font-weight: 500;
}

.nu__tile .track {
  margin-top: 8px;
  height: 5px;
  border-radius: 3px;
}

.nu__meals {
  margin: 16px 16px 0;
  border-radius: var(--r-xxl);
  border: 0.5px solid var(--border);
  background: var(--surface);
  overflow: hidden;
}

.meal {
  padding: 14px 0 6px;
}

.meal + .meal {
  border-top: 0.5px solid var(--border);
}

.meal__head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 16px;
}

.meal__text {
  flex: 1 1 auto;
  min-width: 0;
}

.meal__name {
  font-size: 16px;
  font-weight: 600;
}

.meal__cap {
  margin-top: 2px;
  color: var(--text-2);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

.meal__head .track {
  max-width: 170px;
  margin-top: 8px;
  height: 4px;
}

.meal__add {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 34px;
  padding: 0 14px;
  border-radius: var(--r-pill);
  background: var(--primary-soft);
  color: var(--primary);
  font-size: 13px;
  font-weight: 600;
}

.meal__entries {
  margin-top: 10px;
}

/* "Save this meal" — offered under a meal of two or more items, a star and the
   accent at the leading edge. `mealSave` in the diary. */
.meal__save {
  display: flex;
  align-items: center;
  gap: 6px;
  width: fit-content;
  min-height: 36px;
  margin: 0 16px;
  padding: 0 2px;
  color: var(--primary);
  font-size: 14px;
  font-weight: 600;
}

/* `FoodRow` — name and portion, then the split bar and the P/C/F triad, and
   the calories in a fixed mono column on the right; a rule inset by the row's
   own gutter above each one. */
.frow {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 64px;
  padding: 8px 16px;
}

.frow::before {
  content: '';
  position: absolute;
  top: 0;
  left: 16px;
  right: 16px;
  height: 0.5px;
  background: var(--border);
}

.frow__text {
  flex: 1 1 auto;
  min-width: 0;
  display: grid;
  gap: 4px;
}

.frow__name-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.frow__name {
  flex: 0 1 auto;
  min-width: 0;
  font-size: 15px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.frow__portion {
  flex: 0 0 auto;
  padding: 2px 8px;
  border-radius: 6px;
  border: 0.5px solid var(--border);
  background: var(--surface-alt);
  color: var(--text-2);
  font-size: 11px;
}

.frow__macros {
  display: flex;
  align-items: center;
  gap: 8px;
}

.frow__split {
  display: flex;
  width: 44px;
  height: 4px;
  flex: 0 0 auto;
  border-radius: 2px;
  overflow: hidden;
}

.frow__split i {
  display: block;
  height: 100%;
}

.triad {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  gap: 8px;
}

.triad span {
  flex: 1 1 0;
  display: flex;
  align-items: baseline;
  gap: 4px;
  font-size: 11px;
}

.triad b {
  font-weight: 700;
  letter-spacing: 0.4px;
}

.triad em {
  font-style: normal;
  color: var(--text-2);
}

.frow__kcal {
  width: 56px;
  flex: 0 0 auto;
  text-align: right;
  font-size: 15px;
  font-weight: 600;
}

/* --- The summary — `app/workout/summary.tsx` ------------------------------ */

/* After the redesign: a 16pt gutter and 20pt between blocks; Share as a glass
   disc in its own row; the accent eyebrow (session · when) over the serif
   "Workout Complete"; the volume and its comparison on the tray, with the
   other figures under a rule; then how it felt, the photo, the milestone, who
   trained with you, and the records in a gold-rimmed card — the app's order.
   The muscles, the exercises and Done follow below the fold. */
.sm {
  position: absolute;
  inset: 0;
  padding: 63px 16px 0;
  overflow: hidden;
  display: grid;
  gap: 20px;
  align-content: start;
}

.sm__top {
  display: flex;
  justify-content: flex-end;
}

.sm__hero {
  padding: 0 8px;
}

.sm__eyebrow {
  color: var(--primary);
  font-size: 11px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
}

/* A FIXED screen title, so the serif — at the 50pt this one screen gives it. */
.sm__title {
  margin-top: 8px;
  font-family: var(--font-serif);
  font-size: 50px;
  line-height: 52px;
  font-weight: 400;
  letter-spacing: -1.25px;
}

.sm__tray {
  padding: 16px 20px;
  border-radius: var(--r-xxl);
  border: 0.5px solid var(--border);
  background: var(--surface-alt);
}

.sm__tray-l {
  color: var(--text-2);
  font-size: 11px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
}

.sm__head-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: 8px;
  margin-top: 6px;
}

.sm__headline {
  font-size: 36px;
  line-height: 1.1;
  font-weight: 700;
  letter-spacing: -1.4px;
  font-variant-numeric: tabular-nums;
}

.sm__headline small {
  color: var(--text-2);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0;
}

.sm__delta {
  color: var(--primary);
  font-size: 13px;
  font-weight: 600;
}

.sm__rule {
  display: block;
  height: 0.5px;
  margin: 16px 0;
  background: var(--border-strong);
}

.sm__figs {
  display: flex;
  align-items: center;
  gap: 12px;
}

.sm__fig {
  flex: 1 1 auto;
  min-width: 0;
}

.sm__fig-v {
  font-size: 20px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.sm__fig-v--accent {
  color: var(--primary);
}

.sm__fig-l {
  margin-top: 3px;
  color: var(--text-2);
  font-size: 11px;
  letter-spacing: 1.2px;
  text-transform: uppercase;
}

.sm__div {
  width: 0.5px;
  height: 28px;
  flex: 0 0 auto;
  background: var(--border-strong);
}

.sm__section {
  display: grid;
  gap: 8px;
}

/* A section's heading on the 24pt text gutter — 8pt in from the cards. */
.sm__section-t {
  padding: 0 8px;
  font-size: 17px;
  font-weight: 600;
}

/* `FeelCard`: five faces on the tray, the chosen one filled with the accent. */
.sm__feel {
  display: grid;
  gap: 10px;
}

.sm__feel-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  column-gap: 12px;
}

.sm__feel-head .sm__section-t {
  padding-right: 0;
}

.sm__feel-note {
  padding-right: 8px;
  color: var(--text-2);
  font-size: 13px;
}

.sm__faces {
  display: flex;
  gap: 8px;
}

.sm__face {
  flex: 1 1 0;
  min-width: 0;
  display: grid;
  justify-items: center;
  gap: 4px;
  min-height: 52px;
  padding: 8px 2px;
  border-radius: var(--r-lg);
  background: var(--surface-alt);
  color: var(--text-2);
  font-size: 11px;
  line-height: 16px;
}

.sm__face.on {
  background: var(--primary);
  color: var(--on-filled);
  font-weight: 600;
}

/* The dashed outline is the app's "a picture could go here". */
.sm__photo {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  border-radius: var(--r-lg);
  border: 1px dashed var(--primary-dim);
  color: var(--primary);
}

.sm__photo svg {
  flex: 0 0 auto;
}

.sm__photo-t {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.sm__photo-n {
  font-size: 15px;
  font-weight: 600;
}

.sm__photo-s {
  color: var(--text-3);
  font-size: 11px;
  line-height: 16px;
}

.sm__card {
  border-radius: var(--r-xxl);
  border: 0.5px solid var(--border);
  background: var(--surface);
  overflow: hidden;
}

.sm__card--pr {
  border-color: var(--gold);
}

.sm__card--milestone {
  padding: 4px 0;
}

.sm__row {
  display: flex;
  align-items: center;
  gap: 12px;
}

.sm__row + .sm__row {
  border-top: 0.5px solid var(--border);
}

.sm__row--milestone {
  padding: 16px;
  font-size: 15px;
  font-weight: 600;
  line-height: 22px;
}

.sm__row--recap {
  padding: 8px 12px;
}

.sm__row--pr {
  padding: 12px 16px;
  background: var(--gold-soft);
}

.sm__avatar {
  width: 36px;
  height: 36px;
  flex: 0 0 auto;
  border-radius: var(--r-pill);
  display: grid;
  place-items: center;
  background: var(--primary-soft);
  color: var(--primary);
  font-size: 14px;
  font-weight: 700;
}

.sm__who {
  flex: 1 1 auto;
  min-width: 0;
  display: grid;
  gap: 2px;
}

.sm__who-n {
  font-size: 15px;
  font-weight: 600;
}

.sm__who-m {
  color: var(--text-2);
  font-size: 11px;
}

.sm__vol {
  font-size: 15px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.sm__pr-n {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 15px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sm__pr-d {
  color: var(--gold);
  font-size: 13px;
  font-weight: 600;
}

.sm__trophy {
  color: var(--gold);
  flex: 0 0 auto;
}

/* The macro hues, as `macroColors` assigns them. */
.m-protein {
  color: var(--success);
}
.m-carbs {
  color: var(--warning);
}
.m-fat {
  color: var(--violet);
}
.bg-protein {
  background: var(--success);
}
.bg-carbs {
  background: var(--warning);
}
.bg-fat {
  background: var(--violet);
}

/* --- Feature grid -------------------------------------------------------- */

/* Three columns, not auto-fit: there are twelve cards, and auto-fit hands a wide
   desktop four-across, which is a row too wide to read as a row. Twelve is four
   even rows of three, the way nine was three. */
.grid {
  display: grid;
  gap: var(--s-lg);
  grid-template-columns: 1fr;
}

@media (min-width: 620px) {
  .grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 960px) {
  .grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* A card left alone on the last row takes the whole row rather than sitting in
   one column of three with two holes beside it — in two columns when the count
   is odd, in three when it is one past a multiple of three (sixteen is both). */
@media (min-width: 620px) and (max-width: 959.98px) {
  .grid > .feat:last-child:nth-child(odd) {
    grid-column: 1 / -1;
  }
}

@media (min-width: 960px) {
  .grid > .feat:last-child:nth-child(3n + 1) {
    grid-column: 1 / -1;
  }
}

/*
 * A feature card is read, not pressed — none of the sixteen is a link. They used
 * to firm up under the pointer (surface, border, a warmer chip, the glyph
 * nudging up), which is the answer a control gives: it told a mouse "this does
 * something" and then did nothing when clicked, and on a phone a tap while
 * scrolling left a card lit until the next tap elsewhere. What looks the same
 * must behave the same, so a surface that does nothing shows nothing.
 */
.feat {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xxl);
  padding: var(--s-xl);
}

/* A neutral disc with the accent kept for the glyph — the redesign's settings
   rows carry their icons on a round well. Fifteen blue-tinted discs down the
   page would spend the accent on the least important thing in each card and
   leave nothing for the things that should be blue. */
.feat__ic {
  width: 38px;
  height: 38px;
  border-radius: var(--r-pill);
  display: grid;
  place-items: center;
  background: var(--surface-alt);
  color: var(--primary);
  margin-bottom: var(--s-lg);
}

.feat h3 {
  font-size: 1.0625rem;
  font-weight: 700;
  letter-spacing: var(--tr-tight);
}

.feat p {
  margin-top: var(--s-sm);
  font-size: 0.9375rem;
  line-height: 1.55;
  color: var(--text-2);
}

/* The Pro mark on a card, and on a line of the list below: the same badge the
   plan card wears, a size down, so "Pro" means one thing on the page. */
.feat__pro,
.also__pro {
  display: inline-block;
  vertical-align: 0.15em;
  margin-left: var(--s-xs);
  padding: 1px 6px;
  border-radius: var(--r-pill);
  background: var(--primary-soft);
  color: var(--primary);
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: var(--tr-caps);
  text-transform: uppercase;
}

/* --- The rest of the list -------------------------------------------------- */

/* Two columns of hairline rows under the grid. Bold lead-ins carry the scan;
   the rest of each line is a caption in secondary ink. One column on a phone,
   where two would be too narrow to read a line without wrapping it thrice. */
.also {
  margin-top: clamp(2rem, 4vw, 52px);
  display: grid;
  grid-template-columns: 1fr;
  column-gap: var(--s-3xl);
  border-top: 1px solid var(--border);
}

@media (min-width: 760px) {
  .also {
    grid-template-columns: repeat(2, 1fr);
  }
}

.also li {
  padding: var(--s-md) 0;
  border-bottom: 1px solid var(--border);
  font-size: 0.9375rem;
  line-height: 1.5;
  color: var(--text-2);
}

.also b {
  color: var(--text);
  font-weight: 600;
}

/* --- Split sections ------------------------------------------------------ */

.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: clamp(2rem, 5vw, 80px);
  align-items: center;
}

/* The columns swap, not just the order. Reordering alone leaves the art in the
   `1fr` track and the copy in the `auto` one, where a paragraph resolves to its
   max-content width and rides straight over the art beside it. */
.split--flip {
  grid-template-columns: auto minmax(0, 1fr);
}

.split--flip .split__art {
  order: -1;
}

/* The chart card takes the phone's slot in the "Your numbers" split. It has
   no intrinsic width the way the 320px phone does, so it is given one. */
.split__art--card {
  width: min(100%, 420px);
}

/* The demonstration takes the same slot. A <video> is 1280px wide by nature,
   and in an `auto` track that is the width it asks for — the copy beside it
   was left a word to a line. The wrapper sets the width instead. */
.split__art--demo {
  /* A DEFINITE width, not `min(100%, 480px)`: a length with a percentage in it
     counts as `auto` when the grid sizes its tracks, and `auto` here means the
     video's own 1280px. 480px is definite; the max-width still lets it shrink. */
  width: 480px;
  max-width: 100%;
}

.split__copy h2 {
  margin-top: var(--s-md);
}

/*
 * A filmed demonstration, on its plate.
 *
 * The clips are rendered on pure white and the app frames them in `demoPlate`
 * — the artwork's own white, in both themes, "a deliberate plate, not a
 * missing light value". So the plate here is white on the dark canvas too,
 * with the phone frame's radius so it reads as a screen's worth of the app
 * rather than a video embed. `autoplay muted loop playsinline` is what the app's
 * own player does; under reduced motion the script pauses it on the poster
 * and hands over the controls.
 */
.demo {
  width: 100%;
  margin: 0;
  padding: var(--s-lg);
  border-radius: var(--r-xl);
  background: var(--demo-plate);
  box-shadow: var(--shadow-float);
}

/* 16:9, the clips' own frame; the plate is white so the letterbox is not. */
.demo video {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  height: auto;
  object-fit: contain;
  border-radius: var(--r-lg);
  background: var(--demo-plate);
}

/* The one control on the plate — the same disc the summary's share button is,
   sitting where a thumb lands. Play and pause are both drawn and the state
   decides which shows; the label says what pressing will DO. Under reduced
   motion the clip is already paused and this is how it is started. */
.demo__toggle {
  position: absolute;
  right: calc(var(--s-lg) + 12px);
  bottom: calc(var(--s-lg) + 44px);
  width: 44px;
  height: 44px;
  border-radius: var(--r-pill);
  display: grid;
  place-items: center;
  /* The plate is white in both themes, so the disc takes the LIGHT palette's
     pill, rim and ink whatever the page's theme — glassPillBody,
     glassPillRim and text from the light palette, fixed like the plate itself. */
  background: rgba(255, 255, 255, 0.74);
  backdrop-filter: blur(20px) saturate(160%);
  -webkit-backdrop-filter: blur(20px) saturate(160%);
  border: 1px solid rgba(15, 23, 42, 0.14);
  color: #0b0e12;
  transition: background-color var(--d-fast) ease, transform var(--d-fast) var(--ease-out);
}

@media (hover: hover) {
  .demo__toggle:hover {
    background: rgba(255, 255, 255, 0.92);
  }
}

.demo__toggle:active {
  transform: scale(0.94);
  transition: background-color var(--d-fast) ease, transform 0s;
}

.demo__toggle .i-play {
  display: none;
}

.demo--paused .demo__toggle .i-play {
  display: block;
}

.demo--paused .demo__toggle .i-pause {
  display: none;
}

.demo {
  position: relative;
}

.demo__caption {
  margin-top: var(--s-md);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-md);
  font-size: 0.8125rem;
  color: var(--text-3);
}

.demo__key {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.demo__key i {
  width: 10px;
  height: 10px;
  border-radius: 3px;
}

/* Not the eyebrow: a `.caps` is a paragraph too, and this rule used to outrank
   it, so every section's label above its heading was set as body copy. Inside
   `:where`, so the exclusion adds no weight of its own. */
.split__copy p:where(:not(.caps)) {
  margin-top: var(--s-lg);
  color: var(--text-2);
  font-size: 1.0625rem;
}

.ticks {
  margin-top: var(--s-2xl);
  display: grid;
  gap: var(--s-md);
}

.ticks li {
  display: flex;
  gap: var(--s-md);
  font-size: 0.9375rem;
  color: var(--text-2);
}

.ticks svg {
  flex: 0 0 auto;
  margin-top: 3px;
  color: var(--primary);
}

.ticks b {
  color: var(--text);
  font-weight: 600;
}

@media (max-width: 980px) {
  .split {
    grid-template-columns: minmax(0, 1fr);
  }
  .split__art {
    justify-self: center;
  }
  .split--flip .split__art {
    order: 0;
  }
}

/* --- Stat tiles — `src/components/ui/StatTile.tsx` ----------------------- */

/* 136, not 160. The card is 303px wide inside a 375pt phone, and a 160px
   minimum put the four tiles in one column — four tall cards for four short
   numbers. 136 gives 2 × 2 there and changes nothing in the 380px column the
   chart has on a desktop, where both minimums resolve to two across. */
.tiles {
  display: grid;
  gap: var(--s-md);
  grid-template-columns: repeat(auto-fit, minmax(136px, 1fr));
}

/* A figure on the tray — recessed into the card it sits on, its label in the
   mono micro-caps, as the redesign sets every figure under a heading. */
.tile {
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--s-md);
}

.tile__l {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-2);
}

.tile__v {
  margin-top: 2px;
  font-size: 1.25rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* `text-2`, not `text-3`, for a date somebody is meant to read: the dark
   palette's tertiary grey is 4.38:1 on black — `palette.ts` itself calls that
   "not comfortable" — and it sits under the 4.5 that small text needs. The
   tokens are the app's; which one a line of prose takes is the page's, and the
   same call is made for the footer and the legal pages below. Inside the phone
   mockups the tertiary grey stays — they are drawings of the app, at the app's
   own values, where a caption is a caption. */
.tile__s {
  font-size: 0.6875rem;
  color: var(--text-2);
}

/* --- The chart card ------------------------------------------------------ */

.chartcard {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xxl);
  padding: var(--s-xl);
}

.chartcard__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-md);
  margin-bottom: var(--s-lg);
}

.chartcard__v {
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: var(--tr-tight);
  font-variant-numeric: tabular-nums;
}

.chart {
  width: 100%;
  height: auto;
}

.chartcard__tiles {
  margin-top: 20px;
}

/* --- Plans --------------------------------------------------------------- */

.plans {
  display: grid;
  gap: var(--s-lg);
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  align-items: stretch;
}

/*
 * THE TWO CARDS SHARE THEIR ROWS.
 *
 * The comparison only works read ACROSS — the thirteen lines are the same on both
 * cards, in the same order, so a glance along a row says what Pro adds. That
 * needs the lists to start at the same height, and `plan__note` reserving two
 * lines only held it while the Pro note fitted in two: between about 620 and
 * 1000px it wraps to three and every row below it slips half a line out of
 * register with its twin. A subgrid makes the name, the price, the note and the
 * list the SAME four tracks on both cards, whatever any of them wraps to. Where
 * the plans collapse to one column each card simply spans four rows of its own,
 * and where there is no subgrid the cards fall back to the flex column.
 */
.plan {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xxl);
  padding: var(--s-2xl);
}

@supports (grid-template-rows: subgrid) {
  .plan {
    display: grid;
    grid-template-rows: subgrid;
    grid-row: span 4;
    align-content: start;
  }
}

/* The rim carries it, not a wash. A tint fading down the card put the nine
   feature rows on a ground that changed colour underneath them, which is
   exactly the comparison the two columns exist to make easy. */
.plan--pro {
  border-color: var(--primary-rim);
}

.plan__name {
  display: flex;
  align-items: center;
  gap: var(--s-sm);
  font-size: 1.0625rem;
  font-weight: 700;
}

/* 24, not 32. One card carries a real figure and the other carries a word,
   and set at headline size the word pretends to be a price it is not. */
.plan__price {
  margin-top: var(--s-xs);
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: var(--tr-tight);
}

.plan__price small {
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--text-2);
  letter-spacing: 0;
}

/* The other way to pay, under the price rather than as a row of its own: the
   two cards share four rows, and a fifth on one card only would break the
   reading across. Inside the price row, Basic's "Free" simply gets the height. */
.plan__alt {
  display: block;
  margin-top: 2px;
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0;
  color: var(--text-2);
}

/* Two lines reserved, for the flex fallback; under the subgrid the row is as
   tall as the taller note and this is moot. */
.plan__note {
  margin-top: var(--s-sm);
  min-height: 3.2em;
  font-size: 0.8125rem;
  color: var(--text-2);
}

.plan ul {
  margin-top: var(--s-xl);
  display: grid;
  gap: var(--s-md);
  flex: 1;
}

.plan li {
  display: flex;
  align-items: flex-start;
  gap: var(--s-md);
  font-size: 0.9375rem;
}

/* Greyed, and still readable. `text-disabled` is ink for a control that
   cannot be pressed; these are six lines a person is comparing across the
   two cards, and at 2:1 on the light canvas they were the one part of the
   comparison that could not be read. The dash keeps the disabled grey — it
   is a glyph, not a word, and it is what says "not here". */
.plan li.no {
  color: var(--text-3);
}

.plan li svg {
  flex: 0 0 auto;
  margin-top: 3px;
}

.plan li.yes svg {
  color: var(--success);
}

.plan li.no svg {
  color: var(--text-disabled);
}

.badge {
  padding: 2px 8px;
  border-radius: var(--r-pill);
  background: var(--primary-soft);
  color: var(--primary);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: var(--tr-caps);
  text-transform: uppercase;
}

/* --- Security list ------------------------------------------------------- */

/* Two columns, so four items land as 2 × 2 rather than 3 + 1. */
.secgrid {
  display: grid;
  gap: var(--s-2xl) var(--s-3xl);
  grid-template-columns: 1fr;
}

@media (min-width: 760px) {
  .secgrid {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* The rule is a pseudo-element rather than a border so it can be drawn in —
   see `rule-down` above. Same 2px, same colour, same place. */
.sec {
  position: relative;
  padding: 2px 0 2px var(--s-lg);
}

.sec::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 2px;
  background: var(--primary);
}

.sec h3 {
  font-size: 1.0625rem;
  font-weight: 700;
  letter-spacing: var(--tr-tight);
}

.sec p {
  margin-top: var(--s-sm);
  font-size: 0.9375rem;
  color: var(--text-2);
}

/* --- Closing band -------------------------------------------------------- */

/* Left-aligned, with the actions in their own column. A centred block of three
   stacked lines is the shape every template ends on; ranging it left and
   letting the buttons sit opposite gives the band a structure.

   It was a band of the blue plane, which the app no longer has. It is the
   app's TRAY now — the recessed `surfaceAlt` panel Home sets its figures on,
   `radius.xxl` and a hairline — so the page ends on the material its first
   screen opened with. */
.band {
  position: relative;
  background: var(--bg);
  border-top: 1px solid var(--border);
  padding: clamp(52px, 6vw, 80px) 0;
}

.band__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: clamp(1.5rem, 4vw, 64px);
  align-items: center;
  /* It is also the `.wrap`, whose gutter this padding replaces — so the tray is
     narrowed by that gutter instead, and its edges line up with every other
     section's content rather than running to the window's. */
  width: calc(100% - 2 * var(--s-2xl));
  max-width: calc(var(--maxw) - 2 * var(--s-2xl));
  padding: clamp(28px, 4vw, 52px);
  border-radius: var(--r-xxl);
  border: 1px solid var(--border);
  background: var(--surface-alt);
}

.band h2 {
  max-width: 560px;
  margin-top: var(--s-md);
}

.band p:where(:not(.caps)) {
  max-width: 520px;
  margin-top: var(--s-lg);
  color: var(--text-2);
  font-size: 1.0625rem;
}

/*
 * THE REQUEST FORM.
 *
 * One field and one button, posting to `request.php` on the same host. It
 * replaced a `mailto:` — a control that opened another app, said nothing about
 * having done so, and could never confirm anything. This one answers on the
 * page: the row gives way to a line that says what happened, in the same
 * words the button used (Apple's rule that an action keeps its name through
 * the flow). With scripts off the browser submits the form and the server
 * answers with a page that says the same thing.
 */
.req {
  width: min(100%, 440px);
}

.req__label {
  display: block;
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--text-2);
  margin-bottom: var(--s-sm);
}

.req__row {
  display: flex;
  gap: var(--s-sm);
}

/* `TextField`: the app's input — 52pt tall with a 16pt corner, filled with
   `bgElevated` so it stands off the tray it sits on, and a hairline that turns
   `primary` on focus. */
.req__input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 52px;
  padding: 0 var(--s-lg);
  border-radius: 16px;
  border: 1px solid var(--border);
  background: var(--bg-elevated);
  color: var(--text);
  font: inherit;
  font-size: 1.0625rem;
  transition: border-color var(--d-fast) ease, background-color var(--d-fast) ease;
}

.req__input::placeholder {
  color: var(--text-3);
}

.req__input:focus {
  outline: none;
  border-color: var(--primary);
}

/* Validated as you leave the field, not only when you press the button: an
   address that cannot be one is marked the moment you move on, and unmarked
   the moment it can. `:user-invalid` waits for you to have typed. */
.req__input:user-invalid {
  border-color: var(--danger);
}

.req__input:focus-visible {
  outline: 2px solid var(--primary-rim);
  outline-offset: 2px;
}

/* The honeypot: a field no person sees or tabs to. A script filling every
   input fills this one, and the server drops the request. */
.req__hp {
  position: absolute;
  left: -10000px;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.req__hint,
.req__done,
.req__error {
  margin-top: var(--s-md);
  font-size: 0.8125rem;
  line-height: 1.45;
  color: var(--text-2);
}

.req__done {
  color: var(--text);
  font-weight: 500;
}

.req__error {
  color: var(--danger);
}

.req__done b {
  color: var(--success);
  font-weight: 600;
}

/* While the request is in flight the button says so and cannot be pressed
   twice; when it has landed the row gives way to the confirmation. */
.req[data-state='sending'] .btn {
  opacity: 0.7;
  pointer-events: none;
}

.req[data-state='done'] .req__row,
.req[data-state='done'] .req__label,
.req[data-state='done'] .req__hint {
  display: none;
}

@media (max-width: 480px) {
  .req__row {
    flex-direction: column;
  }
}

.band .btn--secondary {
  background: var(--surface);
  border-color: var(--border);
}

@media (hover: hover) {
  .band .btn--secondary:hover {
    background: var(--surface-pressed);
  }
}

@media (max-width: 820px) {
  .band__inner {
    grid-template-columns: minmax(0, 1fr);
  }
  .band__actions {
    margin-top: var(--s-2xl);
  }
}

/* --- Questions ------------------------------------------------------------ */

/* A column of disclosures, each a hairline row. `<details>`, so it opens
   without a script; the answer materialises rather than snapping, and the
   chevron turns to say which way it will go next. Kept to one column at a
   readable measure — an FAQ is read, not scanned — and CENTRED in the wrap,
   with its heading centred over it: a 760px column ranged left in an 1140px
   row left the right third of the section empty and read as unfinished. */
.faq {
  max-width: 760px;
  margin: 0 auto;
  border-top: 1px solid var(--border);
}

.section__head--centred {
  max-width: 760px;
  margin-left: auto;
  margin-right: auto;
  text-align: center;
}

.faq details {
  border-bottom: 1px solid var(--border);
}

.faq summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-lg);
  min-height: 60px;
  padding: var(--s-md) 0;
  list-style: none;
  cursor: pointer;
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: var(--tr-tight);
  color: var(--text);
  transition: color var(--d-fast) ease;
}

.faq summary::-webkit-details-marker {
  display: none;
}

@media (hover: hover) {
  .faq summary:hover {
    color: var(--primary);
  }
}

.faq summary:active {
  color: var(--primary);
  transition: none;
}

.faq summary svg {
  flex: 0 0 auto;
  color: var(--text-3);
  transition: transform var(--d-base) var(--ease-out), color var(--d-fast) ease;
}

.faq details[open] summary svg {
  transform: rotate(180deg);
  color: var(--primary);
}

/*
 * OPEN AND CLOSE ALONG THE SAME PATH.
 *
 * `<details>` opens by adding a box and closes by removing it, which is an
 * entrance with no exit — the row snapped shut while it had unfolded. Where the
 * engine exposes the content box as `::details-content`, its height is
 * transitioned in both directions, so the answer folds away as it unfolded,
 * over the same 220ms on the same curve. `content-visibility … allow-discrete`
 * is what lets the box stay painted for the length of the closing transition
 * instead of vanishing on the first frame. Elsewhere it still cuts, as before.
 */
@supports selector(::details-content) and (interpolate-size: allow-keywords) {
  .faq details::details-content {
    interpolate-size: allow-keywords;
    block-size: 0;
    overflow: clip;
    /* Closing: the fold gathers pace as it shuts, the unfolding played backwards. */
    transition:
      block-size var(--d-base) var(--ease-in),
      content-visibility var(--d-base) allow-discrete;
  }

  .faq details[open]::details-content {
    block-size: auto;
    transition:
      block-size var(--d-base) var(--ease-out),
      content-visibility var(--d-base) allow-discrete;
  }
}

.faq__body {
  padding: 0 0 var(--s-xl);
  color: var(--text-2);
  font-size: 1.0625rem;
  line-height: 1.6;
  animation: entrance var(--d-base) var(--ease-out) both;
}

.faq__body a {
  color: var(--primary);
}

.faq__body p + p {
  margin-top: var(--s-md);
}

/* --- Footer -------------------------------------------------------------- */

.footer {
  padding: clamp(40px, 5vw, 64px) 0 40px;
  font-size: 0.9375rem;
  color: var(--text-2);
}

/* One column on a phone. `auto-fit` with a 140px minimum still insists on four
   tracks below 600px, which is where the page picked up 30px of sideways
   scroll — the kind of overflow nothing on screen points at. */
.footer__grid {
  display: grid;
  gap: var(--s-2xl);
  grid-template-columns: 1fr;
}

@media (min-width: 720px) {
  .footer__grid {
    grid-template-columns: minmax(200px, 1.4fr) repeat(3, minmax(120px, 1fr));
  }
}

/* Secondary ink for the column labels, the tagline and the closing line —
   they are set to be read, and the tertiary grey is under the floor for small
   text in the dark theme (see `.tile__s`). */
.footer h4 {
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: var(--tr-caps);
  text-transform: uppercase;
  color: var(--text-2);
  margin-bottom: var(--s-md);
}

.footer__tag {
  margin-top: var(--s-md);
  max-width: 300px;
  font-size: 0.9375rem;
  color: var(--text-2);
}

/* Each link is a 44pt row — 15px type at 1.55 leading is 23px, and 0.65rem
   above and below takes it to 44 — rather than a line of text with a gap under
   it. The list gets airier for it, which a footer can afford. */
.footer li a {
  display: inline-block;
  padding: 0.65rem 0;
}

@media (hover: hover) {
  .footer a:hover {
    color: var(--text);
  }
}

.footer__note {
  margin-top: var(--s-3xl);
  padding-top: var(--s-xl);
  border-top: 1px solid var(--border);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-md) var(--s-xl);
  justify-content: space-between;
  font-size: 0.8125rem;
  color: var(--text-2);
}

/* The legal pages' footer is the note alone, so it carries no rule above it
   and no gap for a grid that is not there. */
.footer__note--bare {
  margin-top: 0;
  border-top: 0;
}

/* --- Legal pages --------------------------------------------------------- */

.doc {
  padding-top: calc(var(--nav-h) + 56px);
  padding-bottom: 80px;
}

/* 680, the width every section heading on the landing page already wraps
   at, and about 72 characters a line at this size — 720 ran to 78, which is
   past where the eye finds the next line without looking for it. */
.doc__inner {
  max-width: 680px;
}

.doc h1 {
  font-size: clamp(2rem, 5vw, 2.75rem);
}

/* A section heading inside a document is a label for the paragraphs under it,
   not the title of a screen — so the sans, semibold, as the app's own legal
   viewer sets it. The page's h1 keeps the serif. */
.doc h2 {
  font-family: var(--font-sans);
  font-size: 1.25rem;
  line-height: 1.3;
  font-weight: 600;
  letter-spacing: var(--tr-tight);
  margin-top: var(--s-3xl);
}

/* Primary ink. On the landing page a paragraph is the caption to a heading,
   and secondary grey is right for it; on these pages the paragraphs ARE the
   page — thirty of them, read from top to bottom — and setting the whole
   document one rung down made the one thing here that mattered the one thing
   drawn to be skimmed past. Leading loosens a touch for the same reason. */
.doc p:where(:not(.caps)),
.doc li {
  margin-top: var(--s-md);
  color: var(--text);
  font-size: 1.0625rem;
  line-height: 1.6;
}

.doc ul {
  list-style: disc;
  padding-left: 22px;
}

.doc a,
.faq__body a {
  color: var(--primary);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  text-decoration-color: var(--primary-dim);
}

@media (hover: hover) {
  .doc a:hover,
  .faq__body a:hover {
    text-decoration-color: currentColor;
  }
}

/* `.doc` in front, because `.doc p` above is as specific as a lone class and
   comes first only in the file — it was drawing the date as body copy. */
.doc .doc__meta {
  margin-top: var(--s-md);
  font-size: 0.8125rem;
  color: var(--text-2);
}

/* A run-in heading inside a section — the policy's "what, why, how long"
   entries — one step down from h2, in the same tight tracking. */
.doc h3 {
  margin-top: var(--s-xl);
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: var(--tr-tight);
}

.doc h3 + p {
  margin-top: var(--s-xs);
}

/* The cookie table. It is the one table on the site, and it is read as a
   list: hairlines between rows, the column labels in the caps the app uses
   for a label, and a sideways scroll on a phone rather than a squeeze. */
.doc__table {
  margin-top: var(--s-lg);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.doc table {
  width: 100%;
  min-width: 560px;
  border-collapse: collapse;
  font-size: 0.9375rem;
  line-height: 1.5;
}

.doc th {
  padding: 0 var(--s-md) var(--s-sm) 0;
  text-align: left;
  white-space: nowrap;
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: var(--tr-caps);
  text-transform: uppercase;
  color: var(--text-2);
}

.doc td {
  padding: var(--s-md) var(--s-md) var(--s-md) 0;
  border-top: 1px solid var(--border);
  vertical-align: top;
}

.doc td:last-child,
.doc th:last-child {
  padding-right: 0;
}

.doc td code {
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--surface-alt);
  font-size: 0.875em;
  white-space: nowrap;
}

/* A quiet note — the kind a policy needs once or twice. */
.doc .doc__note {
  margin-top: var(--s-xl);
  padding: var(--s-md) var(--s-lg);
  border-left: 2px solid var(--border-strong);
  color: var(--text-2);
  font-size: 0.9375rem;
}

/* --- Skip link ----------------------------------------------------------- */

/* Off-canvas until it has keyboard focus, then a real button at the top-left.
   The first Tab on the page lands here, and Enter jumps past the nav. */
.skip {
  position: fixed;
  top: var(--s-md);
  left: var(--s-md);
  z-index: 60;
  transform: translateY(calc(-100% - var(--s-xl)));
  transition: transform var(--d-fast) var(--ease-out);
}

/* Set by the script while the panel is open: the page holds still behind it,
   and the bar is glass whatever the scroll — at the top of the page the scroll
   edge leaves it clear over the hero plane, and a clear strip of navy sitting
   on an opaque panel read as a gap. The two colour transitions are for that
   moment only; the bar's ordinary state is driven by scroll, not time. */
html.sheet-open {
  overflow: hidden;
}

html.sheet-open .nav {
  animation: none;
  transition: background-color var(--d-slow) ease, border-color var(--d-slow) ease;
}

.skip:focus-visible {
  transform: none;
  outline-offset: 2px;
}

/* --- Motion on arrival --------------------------------------------------------
 *
 * Everything below the hero arrives as it is scrolled to: a one-shot, time-based
 * animation the script starts when the element is a tenth of the way into the
 * viewport (`.in`), long enough to be seen at 600ms on the app's curve,
 * staggered by 60ms for things that arrive together. It used to be SCROLL-LINKED
 * — `animation-timeline: view()` — mapped onto the first fifth of an element's
 * entry, so it had finished while the element was still at the bottom edge and
 * nobody ever saw one. Hidden-before-arrival states exist only under `html.js`,
 * which the script adds on its first line: with scripts off nothing is ever
 * hidden. What had already been scrolled past when the page loaded is shown at
 * once. The one scroll-linked animation left is the bar's edge, which is a
 * state, not a show.
 *
 * THE SHIMMER WAS NEVER THE TIMELINE; IT WAS THE TEXT CHANGING HOW IT IS DRAWN.
 * While an element animates it is a composited layer of its own, and Chrome on
 * Windows will not draw ClearType (subpixel) text into one — it falls back to
 * greyscale antialiasing, which on white-on-black reads a shade thinner and
 * dimmer. The moment the animation ends the layer is released, the text is
 * rasterised again with ClearType, and it visibly firms up. So every block
 * arriving at the bottom edge looked faded and then "came back" — with the
 * scroll-linked version as it crossed the range, with the timed one 600ms after
 * it appeared, the same swap either way. Dark only, because black-on-white
 * barely changes weight between the two renderings. The fix is to never switch:
 * `main` is a composited layer in its own right, so every line of text on the
 * page is greyscale-antialiased all the time — arriving, at rest, in either
 * theme — and there is nothing left to snap. It is what a Mac has drawn all
 * along (`-webkit-font-smoothing: antialiased`, on body), so Windows now matches
 * the design rather than the other way round. Nothing inside `main` is fixed. The
 * tour's phone is sticky, which a transformed ancestor does not disturb: only a
 * fixed element would be caught by the containing block this creates.
 */
main {
  will-change: transform;
}

@keyframes reveal {
  from {
    opacity: 0;
    transform: translateY(18px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.js .reveal:not(.in) {
  opacity: 0;
}

.reveal.in {
  animation: reveal 0.6s var(--ease-out) both;
  animation-delay: var(--d, 0ms);
}

/* Already past when the page opened: shown, not performed. */
.reveal.in.instant,
.in.instant .chart__line,
.in.instant .chart__area,
.in.instant .chart__dot,
.in.instant .plan li.yes svg,
.in.instant .track > i,
.rule.in.instant,
.sec.in.instant::before {
  animation: none;
}

/*
 * The chart draws itself once the card has arrived — the line uncovered from its
 * first point to its last (its own length, 358.0 for eight points, rounded up),
 * over a second; the tint under it and the dot at its tip only after the line
 * has reached the end, because the area is what the line encloses and the dot
 * is where it stopped.
 */
@keyframes line-in {
  from {
    stroke-dashoffset: 359;
  }
  to {
    stroke-dashoffset: 0;
  }
}

@keyframes settle-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

.chart__line {
  stroke-dasharray: 359;
}

.js .chartcard:not(.in) .chart__line {
  stroke-dashoffset: 359;
}

.js .chartcard:not(.in) .chart__area,
.js .chartcard:not(.in) .chart__dot {
  opacity: 0;
}

.chartcard.in .chart__line {
  animation: line-in 1.1s var(--ease-out) both;
  animation-delay: 0.2s;
}

.chartcard.in .chart__area,
.chartcard.in .chart__dot {
  animation: settle-in 0.4s ease both;
  animation-delay: 1.1s;
}

/*
 * The plan's ticks are MADE, one after another down the card — `DrawnTick`,
 * exactly: the stroke uncovered from its own length (√32 + √145 = 17.7, rounded
 * up to 18) to zero, 520ms as the app's, each row 70ms after the one above so
 * the column appears as a hand would make it. The dashes on Basic do not draw;
 * they are the absence of a mark.
 */
@keyframes tick-in {
  from {
    stroke-dashoffset: 18;
  }
  to {
    stroke-dashoffset: 0;
  }
}

.plan li.yes svg {
  stroke-dasharray: 18;
}

.js .plans:not(.in) .plan li.yes svg {
  stroke-dashoffset: 18;
}

.plans.in .plan li.yes svg {
  animation: tick-in 0.52s var(--ease-out) both;
  animation-delay: calc(0.25s + var(--i, 0) * 70ms);
}

/*
 * The nutrition screen fills once it has arrived — what the app's screen does on
 * mount: every `FillTrack` animates from empty. The tracks grow from the left, a
 * `scaleX` about the leading edge, never a width.
 */
@keyframes track-in {
  from {
    transform: scaleX(0);
  }
}

.ios--nutrition .track > i {
  transform-origin: left center;
}

.js .phone--nutrition:not(.in) .track > i {
  transform: scaleX(0);
}


.phone--nutrition.in .track > i {
  animation: track-in 0.8s var(--ease-out) both;
  animation-delay: 0.4s;
}

/* The hairlines between sections draw from left to right as they arrive, and
   the blue rule beside each security point grows from its top as the point is
   read — structure being ruled onto the page, the one place a line moving is
   the line's own meaning. */
@keyframes rule-across {
  from {
    transform: scaleX(0);
  }
  to {
    transform: none;
  }
}

@keyframes rule-down {
  from {
    transform: scaleY(0);
  }
  to {
    transform: none;
  }
}

.rule {
  transform-origin: left center;
}

.js .rule:not(.in) {
  transform: scaleX(0);
}

.rule.in {
  animation: rule-across 0.8s var(--ease-out) both;
}

.sec::before {
  transform-origin: top center;
}

.js .sec:not(.in)::before {
  transform: scaleY(0);
}

.sec.in::before {
  animation: rule-down 0.6s var(--ease-out) both;
  animation-delay: var(--d, 0ms);
}

/* --- Theme change ---------------------------------------------------------
 *
 * Toggling the palette used to hard-cut from black to white, which is the
 * abrupt brightness jump the reduced-motion guidance warns about — and the one
 * transition that is worth putting on every element, for the `slow` 320ms the
 * script holds this class. Not permanent: a blanket colour transition makes every
 * hover feel a frame late. */
.theme-easing,
.theme-easing *,
.theme-easing *::before,
.theme-easing *::after {
  transition: background-color var(--d-slow) ease, color var(--d-slow) ease,
    border-color var(--d-slow) ease, fill var(--d-slow) ease, stroke var(--d-slow) ease !important;
}

/* --- Accessibility preferences -------------------------------------------- */

/* --- The cookie dialog ------------------------------------------------------
 *
 * The site sets no cookies, and the dialog says so — in the shape a visitor
 * from the EU has learnt to expect, because the shape is what they recognise:
 * three sections (Consent, Details, About), the categories with their counts
 * and a switch each, and Deny / Customize / Allow all across the foot. The
 * words inside the shape are true: two items in local storage, one of them
 * the answer to this very dialog, the other the theme switch's choice, which
 * is the one thing there is to allow or deny. Denying is real — the theme is
 * then not remembered — and so is "Allow selection".
 *
 * It is the app's Sheet: its glass, its floating shadow, its large radius,
 * risen from the foot of the window by the app's 8px on the app's curve, over
 * the app's scrim. The three sections are the app's segmented control at page
 * scale; the switch is the iOS switch, 51 × 31. A native <dialog> shown as a
 * modal, so the browser owns the top layer, the scrim, the focus trap and
 * Escape — which closes it for the visit and stores nothing, which is what
 * "no answer" honestly means. It lives OUTSIDE <main>: main is a composited
 * layer (see "Motion on arrival"), and the top layer is not inside anything.
 */
.consent {
  /* The dialog's own box, reset: it centres with auto margins and draws a
     2px border by default. Bottom-anchored — the top margin is what stays
     auto. */
  position: fixed;
  inset: 0;
  width: min(720px, calc(100% - 2 * var(--s-lg)));
  max-height: calc(100dvh - 2 * var(--s-lg) - env(safe-area-inset-bottom, 0px));
  margin: auto auto calc(var(--s-lg) + env(safe-area-inset-bottom, 0px));
  padding: 0;
  border: 1px solid var(--glass-rim);
  border-radius: var(--r-xl);
  overflow: hidden;
  background: var(--glass-panel-strong);
  backdrop-filter: blur(0px) saturate(100%);
  -webkit-backdrop-filter: blur(0px) saturate(100%);
  box-shadow: var(--shadow-float);
  color: var(--text);
  /* Arrival and exit: opacity over `base`, the rise on the app's curve;
     `display` and `overlay` transition discretely so it stays in the top layer
     for the length of its exit. */
  opacity: 0;
  transform: translateY(var(--rise));
  transition:
    opacity var(--d-base) ease,
    transform var(--d-base) var(--ease-in),
    backdrop-filter var(--d-base) ease,
    -webkit-backdrop-filter var(--d-base) ease,
    display var(--d-base) allow-discrete,
    overlay var(--d-base) allow-discrete;
}

/* In on EASE_OUT, out (the rule above) on its mirror. */
.consent[open] {
  transition:
    opacity var(--d-base) ease,
    transform var(--d-base) var(--ease-out),
    backdrop-filter var(--d-base) ease,
    -webkit-backdrop-filter var(--d-base) ease,
    display var(--d-base) allow-discrete,
    overlay var(--d-base) allow-discrete;
  opacity: 1;
  transform: none;
  backdrop-filter: blur(24px) saturate(160%);
  -webkit-backdrop-filter: blur(24px) saturate(160%);
}

@starting-style {
  .consent[open] {
    opacity: 0;
    transform: translateY(var(--rise));
    backdrop-filter: blur(0px) saturate(100%);
    -webkit-backdrop-filter: blur(0px) saturate(100%);
  }
}

/* The scrim: the app's `overlay`, dimming in with the dialog and lifting with
   it — a modal task pushes the page back, and the push should be seen. The
   literal is the fallback for a browser whose ::backdrop does not yet inherit
   the page's variables. */
.consent::backdrop {
  background: var(--overlay, rgba(0, 0, 0, 0.6));
  opacity: 0;
  transition:
    opacity var(--d-base) ease,
    display var(--d-base) allow-discrete,
    overlay var(--d-base) allow-discrete;
}

.consent[open]::backdrop {
  opacity: 1;
}

@starting-style {
  .consent[open]::backdrop {
    opacity: 0;
  }
}

@supports not (backdrop-filter: blur(1px)) {
  .consent {
    background: var(--bg-elevated);
  }
}

.consent__inner {
  display: grid;
  gap: var(--s-lg);
  padding: var(--s-xl) var(--s-2xl) var(--s-2xl);
}

.consent__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-md);
}

.consent__brand {
  font-size: 0.9375rem;
  pointer-events: none;
}

.consent__brand .brand__mark {
  width: 22px;
  height: 22px;
  border-radius: 6px;
}

/* `SegmentedControl`, at page scale, as the redesign draws it: a pill-shaped
   tray with a hairline, and the chosen segment a pill of the card colour with
   `text` on it — the accent is for actions, not for "which of these". */
.consent__seg {
  display: flex;
  gap: 3px;
  padding: 3px;
  border-radius: var(--r-pill);
  border: 1px solid var(--border);
  background: var(--surface-alt);
}

.consent__tab {
  flex: 1 1 0;
  min-height: 40px;
  padding: 0 var(--s-md);
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  background: transparent;
  font: inherit;
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--text-2);
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--d-fast) ease, color var(--d-fast) ease;
}

@media (hover: hover) {
  .consent__tab:hover {
    color: var(--text);
  }
}

/* The segment gives to the touch, on the way down, instantly. */
.consent__tab:active {
  transform: scale(0.96);
  transition: none;
}

.consent__tab[aria-selected='true'] {
  color: var(--text);
  font-weight: 600;
  border-color: var(--border);
  background: var(--surface);
}

/* One section at a time. It scrolls if it must; the actions never do. */
.consent__panel {
  max-height: min(42dvh, 340px);
  overflow: auto;
  /* Room for the scrollbar it grows on a phone, so it never sits on a switch. */
  padding-right: var(--s-sm);
  scrollbar-width: thin;
  font-size: 0.9375rem;
  line-height: 1.5;
  color: var(--text-2);
}

.consent__panel p {
  margin: 0;
}

.consent__panel p + p {
  margin-top: var(--s-md);
}

.consent__panel b {
  color: var(--text);
  font-weight: 600;
}

.consent__panel a {
  color: var(--text);
  text-decoration: underline;
  text-decoration-color: var(--border-strong);
  text-underline-offset: 3px;
}

@media (hover: hover) {
  .consent__panel a:hover {
    text-decoration-color: currentColor;
  }
}

/* A sheet's title is the sans, as every sheet in the app sets it. */
.consent__title {
  margin: 0 0 var(--s-sm);
  font-family: var(--font-sans);
  font-size: 1.25rem;
  line-height: 1.25;
  font-weight: 600;
  letter-spacing: var(--tr-tight);
  color: var(--text);
}

/* The categories: a list of rows between hairlines, as a settings screen is. */
.consent__list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.consent__cat {
  padding: var(--s-md) 0;
  border-top: 1px solid var(--border);
}

.consent__cat:first-child {
  padding-top: 0;
  border-top: 0;
}

.consent__cat-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-md);
  min-height: 31px;
}

.consent__cat-name {
  display: inline-flex;
  align-items: center;
  gap: var(--s-sm);
  font-weight: 600;
  color: var(--text);
}

.consent__count {
  display: inline-grid;
  place-items: center;
  min-width: 22px;
  height: 22px;
  padding: 0 7px;
  border-radius: var(--r-pill);
  background: var(--surface-alt);
  font-size: 0.75rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-2);
}

.consent__state {
  font-size: 0.875rem;
  color: var(--text-2);
}

.consent__cat-desc {
  margin: var(--s-xs) 0 0;
  font-size: 0.875rem;
}

/* The iOS switch, at its own size: 51 × 31, a 27pt knob, the track `primary`
   when on and the pressed surface when off. The knob's travel is on the app's
   curve over `base`. */
.switch {
  position: relative;
  flex: 0 0 auto;
  width: 51px;
  height: 31px;
  padding: 0;
  border: 0;
  border-radius: var(--r-pill);
  background: var(--surface-pressed);
  cursor: pointer;
  transition: background-color var(--d-base) ease;
}

.switch[aria-checked='true'] {
  background: var(--primary);
}

.switch__knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 27px;
  height: 27px;
  border-radius: 50%;
  background: #ffffff;
  box-shadow: 0 3px 8px rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.16);
  transition: transform var(--d-base) var(--ease-out), width var(--d-fast) var(--ease-out);
}

.switch[aria-checked='true'] .switch__knob {
  transform: translateX(20px);
}

/* Pressed, the knob stretches toward where it is going — the iOS switch's own
   tell — and springs back to round on release. */
.switch:active .switch__knob {
  width: 33px;
}

.switch[aria-checked='true']:active .switch__knob {
  transform: translateX(14px);
}

/* Three actions across the foot, above a hairline; the middle one is
   Customize until the Details section is showing, then Allow selection. */
.consent__actions {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-md);
  padding-top: var(--s-lg);
  border-top: 1px solid var(--border);
}

.consent__actions .btn {
  min-width: 0;
  padding-left: var(--s-md);
  padding-right: var(--s-md);
}

/* `.btn` sets a display of its own, which would otherwise outrank the
   attribute the script hides the spare action with. */
.consent__actions .btn[hidden] {
  display: none;
}

@media (max-width: 600px) {
  .consent__inner {
    padding: var(--s-lg) var(--s-lg) var(--s-xl);
  }
  .consent__panel {
    max-height: 34dvh;
  }
  .consent__actions {
    grid-template-columns: 1fr;
  }
  /* Stacked, the affirmative comes first and the refusal last, as the app's
     sheets order theirs. */
  .consent__actions .btn--primary {
    order: -1;
  }
  .consent__actions [data-consent-deny] {
    order: 1;
  }
}

/* The reopen control in the footer: a button wearing a link's clothes, in the
   list with the two documents. Hidden until the script is there to answer it. */
.footer__btn {
  display: inline-block;
  padding: 0.65rem 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  cursor: pointer;
  transition: color var(--d-fast) ease;
}

@media (hover: hover) {
  .footer__btn:hover {
    color: var(--text);
  }
}

.footer__btn:active {
  color: var(--primary);
  transition: none;
}

.footer__note .footer__btn {
  margin: -0.65rem 0;
}

@media (prefers-reduced-motion: reduce) {
  .hero__copy > *,
  .hero__art {
    animation: none;
  }
  .btn:active,
  .themetoggle:active,
  .menu__btn:active {
    transform: none;
  }
  .rest__ring [data-rest-arc] {
    transition: none;
  }
  .consent,
  .consent[open],
  .consent::backdrop,
  .consent__tab,
  .switch,
  .switch__knob,
  .footer__btn {
    transition: none;
  }
  .consent__tab:active {
    transform: none;
  }
  .switch:active .switch__knob {
    width: 27px;
  }
  .skip {
    transition: none;
  }
  /* The icons still swap and the menu still opens; they just do it in a
     frame, the way `<details>` does on its own. */
  .themetoggle svg {
    transition: none;
  }
  .sheet,
  .sheet:popover-open,
  .sheet::backdrop,
  .menu__btn svg {
    transition: none;
  }
  .sheet .sheet__list li {
    animation: none;
  }
  .faq details::details-content,
  .faq details[open]::details-content {
    transition: none;
  }
  .faq__body {
    animation: none;
  }
  /* Nothing rises, fills or fades with the scroll; everything is simply at its
     resting value — the tracks full, the cards in place. */
  .reveal,
  .ios--home .wcard,
  .ios--nutrition .track > i,
  .chart__line,
  .chart__area,
  .chart__dot,
  .plan li.yes svg,
  .sec::before,
  .rule {
    animation: none;
  }
  /* And nothing waits hidden for an arrival that will not be performed. */
  .js .reveal:not(.in),
  .js .chartcard:not(.in) .chart__area,
  .js .chartcard:not(.in) .chart__dot {
    opacity: 1;
  }
  .js .chartcard:not(.in) .chart__line {
    stroke-dashoffset: 0;
  }
  .js .plans:not(.in) .plan li.yes svg {
    stroke-dashoffset: 0;
  }

  .js .phone--nutrition:not(.in) .track > i,
  .js .rule:not(.in),
  .js .sec:not(.in)::before {
    transform: none;
  }
}

/* The glass surfaces go frosted-solid: the same colour, at full opacity,
   with no blur to read through. Layout is untouched. The bar's scroll-edge
   animation is switched off with them, because an animation outranks these
   declarations and would otherwise carry the glass back in. */
@media (prefers-reduced-transparency: reduce) {
  .gdisc,
  .gpill,
  .demo__toggle {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .gdisc,
  .gpill {
    background: var(--bg-elevated);
  }
  .demo__toggle {
    background: #ffffff;
  }
  .nav,
  .tb,
  .rest,
  .sheet,
  .sheet:popover-open,
  .consent,
  .consent[open] {
    background: var(--bg-elevated);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .nav {
    animation: none;
  }
}

/* Every hairline becomes a defined edge, and each grey steps up one rung:
   secondary ink becomes primary, tertiary becomes what secondary was. The
   tertiary value is spelled out per theme because a custom property cannot
   read its own previous value. */
@media (prefers-contrast: more) {
  :root {
    --border: var(--border-strong);
    --text-2: var(--text);
    --text-3: #d0d1d6;
    --on-hero: var(--text);
  }
  :root[data-theme='light'] {
    --text-3: #52525b;
  }
  .nav,
  .tb,
  .rest,
  .sheet,
  .sheet:popover-open,
  .consent,
  .consent[open] {
    background: var(--bg-elevated);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border-color: var(--border-strong);
  }
  .switch {
    outline: 1px solid var(--border-strong);
  }
  .sheet {
    outline: 1px solid var(--border-strong);
  }
  /* The chosen segment and the field take the strong rim, as the cards do. */
  .req__input,
  .seg span.on,
  .consent__tab[aria-selected='true'] {
    border-color: var(--border-strong);
  }
  .nav {
    animation: none;
  }
  .feat,
  .plan,
  .tile,
  .chartcard,
  .card {
    border-color: var(--border-strong);
  }
}

/* --- Small screens: the phone scales rather than overflows ---------------- */

@media (max-width: 480px) {
  .wrap {
    padding: 0 var(--s-lg);
  }
  .nav__inner {
    padding: 0 var(--s-lg);
    gap: var(--s-md);
  }
  /* Brand, two 44px buttons and the short call to action come to 365px with
     8px gaps — inside a 375pt iPhone with room to spare, and not with 12. */
  .nav__actions {
    gap: var(--s-sm);
  }
  .hero__cta .btn {
    flex: 1 1 auto;
  }
}

/* Below this the 350px phone plus its gutters no longer fits a 375pt iPhone, so
   the frame narrows and the canvas zooms to match — 290px for 393pt. The two
   numbers are one ratio and must move together. */
@media (max-width: 400px) {
  .phone {
    --scr-w: 290px;
    --k: 0.7379;
  }
}

/* ==========================================================================
   The 3D tour — assets/stage.js builds it.

   Nothing here applies without those scripts: `.js3d` is the wide layout, one
   phone pinned beside the words; `.js3d-narrow` keeps each chapter's own phone.
   With scripts off, the chapters are ordinary splits with a flat phone each.
   ========================================================================== */

.tour {
  position: relative;
  /* A phone leaning towards the edge must not make the page scroll sideways.
     `clip`, not `hidden`: hidden would make this a scroller and break the pin. */
  overflow-x: clip;
}

.chapter__note {
  margin-top: var(--s-lg);
  font-size: 14px;
  line-height: 1.45;
  color: var(--text-3);
}

/* --- The object ----------------------------------------------------------- */

.dev {
  position: relative;
  flex: none;
  perspective: 1700px;
  perspective-origin: 50% 46%;
}

.dev__body {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  will-change: transform;
}

/* One face of the band. Its colour is worked out per frame from its own normal,
   which is what makes it read as a turned metal edge and not a stripe. */
.dev__q {
  position: absolute;
  left: 50%;
  top: 50%;
  display: block;
  background: #66676c;
  backface-visibility: hidden;
}

.dev__q--btn {
  border-radius: 2px;
}

.dev__core {
  position: absolute;
  inset: 0;
  display: block;
  border-radius: 54px;
  background: #16171a;
}

.dev .phone {
  position: absolute;
  top: 0;
  left: 0;
  box-shadow: none;
  transform-origin: 0 0;
  transform: translateZ(calc(var(--dev-d) / 2)) scale3d(calc(1 / var(--hd)), calc(1 / var(--hd)), calc(1 / var(--hd)));
  /* Keeps the screen from being redrawn on every frame of a turn: redrawing it at each small
     change of scale is what makes fine detail shimmer. */
  will-change: transform;
  border-color: #55565c;
  /* Hardware black in both themes: this is an object now, not a panel of the page. */
  background: #0b0b0d;
}

/* A screen in 3D is drawn once, at the size it has on the page, and then resampled by the lift
   and the turn — on a screen under 2x, and more so with the browser zoomed, the type went soft.
   Drawn at twice the size and shrunk back, the resampling takes detail away instead of making it
   up, and the text stays sharp. Not on a 2x or 3x screen, which is sharp already and where a
   doubled phone costs four times the memory. The z is shrunk with the rest, so the logger's
   lifted layers keep their height. */
@media (max-resolution: 1.99dppx), (-webkit-max-device-pixel-ratio: 1.99) {
  .dev .phone {
    --hd: 2;
  }
}

/* A phone that is folded away, or whose chapter this is not. */
.dev__body.is-off,
.dev__body.is-off * {
  visibility: hidden !important;
}

/* The screen on its way out lies over the one arriving and thins out to reveal it. Only its
   glass is left: a body drawn over the arriving phone would hide the very screen coming in. */
.dev__body.is-leaving > .dev__q,
.dev__body.is-leaving > .dev__core {
  visibility: hidden;
}

/* The fan on a narrow window opens from the foot of the phones, like a hand of cards. */
.dev__body--fan {
  transform-origin: 50% 100%;
}


/* A screen moved into the object leaves its description behind, in reading order. */
.dev__alt {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

/* --- The logger, coming apart ---------------------------------------------
   Everything from the front face down to the set rows joins the 3D scene, so a
   row can lift off its card. That rules out anything that would flatten it:
   no clipping and no glass on the way down, so the screen rounds its own corners
   and the lifted pieces are solid.

   THE TOP OF THE SCREEN STAYS ON THE GLASS. The top bar used to lift with the rest,
   and perspective carries anything nearer the eye away from the centre: it rode up
   over the Dynamic Island, dark on black, so the island seemed to stretch across the
   phone and shift about as the page scrolled. Nothing above the exercise card lifts,
   and the status bar and the bar under it stay where the phone puts them. */

.dev .phone--split,
.dev .phone--split .phone__screen,
.dev .phone--split .ios,
.dev .phone--split .lg,
.dev .phone--split .lg__list,
.dev .phone--split .exb {
  transform-style: preserve-3d;
}

.dev .phone--split .phone__screen,
.dev .phone--split .ios,
.dev .phone--split .lg {
  overflow: visible;
}

.dev .phone--split .phone__screen {
  background: transparent;
}

.dev .phone--split .ios {
  border-radius: calc(44px / var(--k));
}

.dev .phone--split .gdisc,
.dev .phone--split .gpill,
.dev .phone--split .rest {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.dev .phone--split .gdisc,
.dev .phone--split .gpill {
  background: var(--bg-elevated);
}

.dev .phone--split .rest {
  background: var(--bg-elevated);
  transform: translateZ(calc(var(--e, 0) * 128px));
  box-shadow: 0 calc(var(--e, 0) * 22px) calc(var(--e, 0) * 44px) rgba(0, 0, 0, calc(0.2 + var(--e, 0) * 0.34));
}


.dev .phone--split .exb {
  transform: translateZ(calc(var(--e, 0) * 26px));
}

.dev .phone--split .srow {
  transform: translateZ(calc(var(--e, 0) * 30px));
}

.dev .phone--split .srow:not(.srow--done) {
  transform: translateZ(calc(var(--e, 0) * 66px));
  border-radius: 14px;
  background: var(--bg-elevated);
  box-shadow: 0 calc(var(--e, 0) * 14px) calc(var(--e, 0) * 26px) rgba(0, 0, 0, calc(var(--e, 0) * 0.3));
}

.dev .phone--split .exb__again {
  transform: translateZ(calc(var(--e, 0) * 44px));
}

/* --- The wide layout: one phone, pinned ------------------------------------ */

/* A layer the height of the tour, with the phone pinned inside it. The pin can only travel as far
   as this layer goes, so when the tour ends the phone scrolls away with it. It used to be the
   sticky element itself, with a negative margin so it took no room; a sticky element's travel is
   measured by its margin box, so that margin let it run a whole window past the end of the tour,
   straight over the feature cards. */
.js3d .tour__stage {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: block;
  pointer-events: none;
}

/* Whatever a phone or its shadow might do, the page after the tour is above it. */
.js3d .sheet-page {
  z-index: 4;
}

/* The whole window, one camera over all six phones. Each is placed by its transform from the
   top-left corner, so the arc can use the width of the page and the pinned phone its right side.
   They are not one 3D scene with each other, on purpose: each paints whole, in its layer, so two
   neighbours can never slice through one another. */
.js3d .stage {
  position: sticky;
  top: 0;
  width: 100%;
  height: 100vh;
}

.stage > .dev__body {
  inset: auto;
  left: 0;
  top: 0;
}

.stage__floor {
  position: absolute;
  left: 0;
  top: 0;
  display: block;
  width: 330px;
  height: 46px;
  background: radial-gradient(closest-side, rgba(0, 0, 0, 0.42), rgba(0, 0, 0, 0));
  transform-origin: 0 0;
}

:root[data-theme='light'] .stage__floor {
  background: radial-gradient(closest-side, rgba(24, 24, 27, 0.2), rgba(24, 24, 27, 0));
}

/* The phone is in the stage now. What is left in the words' column is its
   description, for a screen reader. */
.js3d .tour .split__art,
.js3d .tour .hero__art {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  animation: none;
}

.js3d .tour .rule {
  display: none;
}

.js3d .tour .hero__grid,
.js3d .tour .split,
.js3d .tour .split--flip {
  display: block;
}

.js3d .tour .hero__copy,
.js3d .tour .split__copy {
  position: relative;
  z-index: 1;
  /* Clear of the phone, which leans out of its own 350px when its layers come apart. */
  max-width: min(560px, calc(100% - 470px));
}

/* The words over the arc, centred on it, on one line where the window allows. The section runs
   past the fold because the fold of the arc is scrolled through before the first chapter. */
.js3d .tour .hero {
  min-height: 126vh;
  padding-top: calc(var(--nav-h) + clamp(1.75rem, 5vh, 56px));
  padding-bottom: 0;
}

.js3d .tour .hero__copy {
  max-width: 1040px;
  margin: 0 auto;
  text-align: center;
}

.js3d .tour .hero h1 {
  margin-top: 0;
}

.js3d .tour .hero h1 br {
  display: none;
}

.js3d .tour .hero .lede {
  max-width: 620px;
  margin: var(--s-lg) auto 0;
}

.js3d .tour .hero__cta {
  justify-content: center;
  margin-top: var(--s-2xl);
}

.js3d .tour .section {
  display: flex;
  align-items: center;
  min-height: 118vh;
  padding: 16vh 0;
}

.js3d .tour .section--long {
  min-height: 160vh;
}


/* --- The narrow layout: every chapter keeps its phone ----------------------- */


.js3d-narrow .split__art .dev,
.js3d-narrow .hero__art .dev {
  margin: 0 auto;
}

/* --- The Lock Screen and the Home Screen: a wallpaper between them -----------
   The same one on both, as a phone has. */

.wall {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(90% 46% at 18% 6%, #4a68e8 0%, rgba(74, 104, 232, 0) 70%),
    radial-gradient(80% 42% at 92% 34%, #1f7fdc 0%, rgba(31, 127, 220, 0) 72%),
    radial-gradient(120% 60% at 50% 110%, #0d1433 0%, rgba(13, 20, 51, 0) 70%),
    linear-gradient(180deg, #16204f 0%, #090c1c 100%);
}

.ios--lock,
.ios--widgets {
  --text: #f7f7f8;
  color: #f7f7f8;
  background: #090c1c;
}

/* --- The Lock Screen — `WorkoutLockScreenView` ------------------------------- */

.lk__clock {
  position: absolute;
  top: 68px;
  left: 0;
  right: 0;
  text-align: center;
}

.lk__date {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-size: 19px;
  font-weight: 600;
  color: rgba(247, 247, 248, 0.92);
}

.lk__inline {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.lk__ic {
  display: inline-flex;
  width: 15px;
  height: 15px;
}

.lk__ic svg {
  width: 100%;
  height: 100%;
}

.lk__ic--sm {
  width: 13px;
  height: 13px;
}

.lk__time {
  margin-top: -2px;
  font-size: 92px;
  line-height: 1.02;
  font-weight: 600;
  letter-spacing: -2px;
  font-variant-numeric: tabular-nums;
  color: rgba(247, 247, 248, 0.96);
}

.lk__faces {
  position: absolute;
  top: 190px;
  left: 0;
  right: 0;
  display: flex;
  justify-content: center;
  gap: 12px;
}

.lk__circle,
.lk__rect {
  height: 72px;
  color: #f7f7f8;
  background: rgba(255, 255, 255, 0.14);
}

.lk__circle {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 72px;
  gap: 1px;
  border-radius: 50%;
}

.lk__circle-n {
  font-size: 22px;
  line-height: 1;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.lk__rect {
  width: 170px;
  padding: 10px 13px;
  border-radius: 18px;
}

.lk__rect-h {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  font-weight: 600;
}

.lk__rect-t {
  margin-top: 1px;
  font-size: 15px;
  font-weight: 700;
}

.lk__rect-d {
  margin-top: 1px;
  font-size: 12px;
  font-weight: 500;
  opacity: 0.85;
}

.lk__corner {
  position: absolute;
  bottom: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  color: #f7f7f8;
  background: rgba(255, 255, 255, 0.16);
}

.lk__corner svg {
  width: 22px;
  height: 22px;
}

.lk__corner--l {
  left: 42px;
}

.lk__corner--r {
  right: 42px;
}

/* The card. The app's dark palette whatever the phone is set to, under its own
   `la*` names in `targets/widget/expo-target.config.js`. */
.la {
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 100px;
  padding: 10px 16px;
  border-radius: 26px;
  background: #1c1d21;
  color: #f7f7f8;
  box-shadow: 0 10px 34px rgba(0, 0, 0, 0.34);
}

.la__head {
  display: flex;
  align-items: center;
  gap: 12px;
}

.la__ring {
  position: relative;
  flex: none;
  width: 40px;
  height: 40px;
}

.la__ring svg {
  display: block;
  width: 100%;
  height: 100%;
}

.la__arc {
  /* Three of seven sets, until the script is counting. */
  stroke-dashoffset: 64.63;
  transition: stroke-dashoffset 0.5s var(--ease-out);
}

.la__glyph {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #f7f7f8;
}

.la__glyph svg {
  width: 19px;
  height: 19px;
}

.la__who {
  flex: 1 1 auto;
  min-width: 0;
}

.la__title {
  font-size: 17px;
  line-height: 1.2;
  font-weight: 600;
}

.la__cap {
  display: flex;
  gap: 5px;
  margin-top: 2px;
  font-size: 13px;
  line-height: 1.2;
  font-weight: 500;
  color: #b7b8bf;
  font-variant-numeric: tabular-nums;
}

.la__cap-clock {
  display: inline-flex;
  gap: 5px;
  opacity: 0;
  transition: opacity var(--d-base) var(--ease-out);
}

.is-resting .la__cap-clock {
  opacity: 1;
}

/* One big number at a time: the session between sets, the countdown at rest. */
.la__big {
  position: relative;
  flex: none;
  width: 112px;
  height: 40px;
  font-size: 32px;
  line-height: 40px;
  font-weight: 600;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.la__big-session,
.la__big-rest {
  position: absolute;
  right: 0;
  top: 0;
  transition: opacity var(--d-base) var(--ease-out);
}

.la__big-rest {
  color: #b3c3ff;
  opacity: 0;
}

.is-resting .la__big-rest {
  opacity: 1;
}

.is-resting .la__big-session {
  opacity: 0;
}

.la__panel {
  position: relative;
  height: 84px;
  margin-top: 8px;
  border-radius: 16px;
  background: #2b2c31;
}

.la__state {
  position: absolute;
  inset: 0;
  padding: 9px 12px;
  transition: opacity var(--d-base) var(--ease-out), visibility var(--d-base);
}

.la__state--next {
  display: flex;
  align-items: center;
  gap: 12px;
}

/* One state at a time, and the hidden ones cannot be pressed. */
.la__state--rest,
.la__state--handoff {
  opacity: 0;
  visibility: hidden;
}

.is-resting .la__state--rest,
.is-handoff:not(.is-resting) .la__state--handoff {
  opacity: 1;
  visibility: visible;
}

.is-resting .la__state--next,
.is-handoff .la__state--next {
  opacity: 0;
  visibility: hidden;
}

.la__text {
  flex: 1 1 auto;
  min-width: 0;
}

.la__heading,
.la__then {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: #b7b8bf;
  font-weight: 500;
}

.la__heading {
  font-size: 13px;
  line-height: 1.25;
}

.la__figures {
  font-size: 28px;
  line-height: 1.18;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.la__figures--sm {
  font-size: 22px;
}

.la__then {
  margin-top: 2px;
  font-size: 12px;
  line-height: 1.2;
}

/* The tick: the one filled disc on the card. */
.la__tick {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: #b3c3ff;
  color: #131a35;
  transition: transform 0.16s var(--ease-out);
}

.la__tick svg {
  width: 26px;
  height: 26px;
}

.la__tick--sm {
  width: 44px;
  height: 44px;
  margin-left: 4px;
}

.la__tick--sm svg {
  width: 20px;
  height: 20px;
}

.la__rest-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.la__ctl {
  position: relative;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: #3e3f45;
  color: #f7f7f8;
}

.la__ctl svg {
  width: 15px;
  height: 15px;
}

.la__ctl--accent {
  color: #b3c3ff;
}

.la__ctl--accent svg {
  width: 26px;
  height: 26px;
}

.la__ctl small {
  position: absolute;
  font-size: 8.5px;
  font-weight: 700;
}

.la__bar {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 7px;
  font-size: 11px;
  line-height: 1;
  font-weight: 600;
  color: #b7b8bf;
}

.la__bar i {
  flex: 1 1 auto;
  display: block;
  height: 4px;
  overflow: hidden;
  border-radius: 2px;
  background: #3e3f45;
}

.la__bar b {
  display: block;
  height: 100%;
  background: #b3c3ff;
  transform-origin: left;
  transform: scaleX(1);
  transition: transform 1s linear;
}


/* --- The Home Screen — `targets/widget/*Widget.swift` --------------------------
   The widget colours are the target's own, in light and dark, so these follow the
   page's theme the way a widget follows the phone's. */

.ios--widgets {
  --wg: #26272b;
  --wg-tray: #37383d;
  --wg-p: #b3c3ff;
  --wg-on: #131a35;
  --wg-t: #f7f7f8;
  --wg-2: #d0d1d6;
  --wg-3: #b7b8bf;
  --wg-gold: #e8bc62;
  --wg-protein: #9ed9a9;
  --wg-carbs: #f0be74;
  --wg-fat: #a855f7;
}

:root[data-theme='light'] .ios--widgets {
  --wg: #ffffff;
  --wg-tray: #e8e8ec;
  --wg-p: #2747c6;
  --wg-on: #ffffff;
  --wg-t: #18181b;
  --wg-2: #52525b;
  --wg-3: #63636d;
  --wg-gold: #85570a;
  --wg-protein: #127034;
  --wg-carbs: #a04a08;
}

.hs {
  position: absolute;
  top: 60px;
  left: 27.5px;
  right: 27.5px;
  display: grid;
  grid-template-columns: 158px 158px;
  justify-content: space-between;
}

.wg {
  display: flex;
  height: 158px;
  padding: 16px;
  border-radius: 22px;
  background: var(--wg);
  color: var(--wg-t);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
}

.wg--s {
  flex-direction: column;
  min-width: 0;
}

.wg--m {
  grid-column: 1 / -1;
  gap: 16px;
}

.wg__name {
  height: 23px;
  padding-top: 5px;
  font-size: 11px;
  line-height: 13px;
  font-weight: 500;
  text-align: center;
  color: #f7f7f8;
}

.wg__name--wide {
  grid-column: 1 / -1;
}

.wg__col {
  display: flex;
  flex-direction: column;
  flex: 0 0 auto;
  width: 126px;
  min-width: 0;
}

.wg__spring {
  flex: 1 1 auto;
  min-height: 4px;
}

.wg__eyebrow {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  line-height: 1.2;
  font-weight: 600;
  color: var(--wg-p);
}

.wg__ic {
  display: inline-flex;
  flex: none;
  width: 13px;
  height: 13px;
}

.wg__ic svg {
  width: 100%;
  height: 100%;
}

.wg__ic--gold {
  color: var(--wg-gold);
  width: 11px;
  height: 11px;
}

.wg__title {
  font-size: 18px;
  line-height: 1.2;
  font-weight: 600;
}

.wg__detail,
.wg__sub {
  margin-top: 3px;
  font-size: 12px;
  line-height: 1.2;
  font-weight: 500;
  color: var(--wg-2);
  white-space: nowrap;
}

.wg__sub {
  margin-top: 0;
}

.wg__list {
  flex: 1 1 auto;
  min-width: 0;
  padding-top: 2px;
  font-size: 13px;
  font-weight: 500;
}

.wg__list > div {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 8px;
  white-space: nowrap;
  overflow: hidden;
}

.wg__list i {
  flex: none;
  display: block;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--wg-p);
}

.wg__fig {
  font-size: 42px;
  line-height: 1.02;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.wg-d {
  display: block;
  border-radius: 3.5px;
  background: var(--wg-tray);
}

.wg-d--on {
  background: var(--wg-p);
}

.wg-d--today {
  box-shadow: inset 0 0 0 1.5px var(--wg-t);
}

.wg-d--unknown {
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--wg-tray);
}

.wg-d--blank {
  background: transparent;
}

.wg-week__h {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  column-gap: 1.5px;
  margin-bottom: 2.5px;
  font-size: 8px;
  line-height: 1;
  font-weight: 600;
  text-align: center;
  color: var(--wg-3);
}

.wg-week__r {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  column-gap: 1.5px;
}

.wg-week__r .wg-d {
  height: 14px;
}

.wg__streak {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 10px;
  line-height: 1.2;
  font-weight: 500;
  color: var(--wg-2);
  white-space: nowrap;
}

.wg__disc {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: var(--wg-p);
  color: var(--wg-on);
}

.wg__disc svg {
  width: 21px;
  height: 21px;
}

.wg__start {
  font-size: 16px;
  line-height: 1.2;
  font-weight: 600;
}

.wg__kcal {
  font-size: 32px;
  line-height: 1.1;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.wg-bar {
  display: block;
  height: 6px;
  margin-top: 8px;
  overflow: hidden;
  border-radius: 3px;
  background: var(--wg-tray);
}

.wg-bar b {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: var(--wg-p);
}

.wg-bar--thin {
  grid-column: 1 / -1;
  height: 5px;
  margin-top: 3px;
}

.wg-bar--protein b {
  background: var(--wg-protein);
}

.wg-bar--carbs b {
  background: var(--wg-carbs);
}

.wg-bar--fat b {
  background: var(--wg-fat);
}

.wg__left {
  margin-top: 5px;
  font-size: 11px;
  line-height: 1.2;
  font-weight: 500;
  color: var(--wg-2);
}

.wg__macros {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.wg-m {
  display: grid;
  grid-template-columns: auto 1fr;
  font-size: 11px;
  line-height: 1.15;
}

.wg-m span {
  font-weight: 600;
}

.wg-m em {
  font-style: normal;
  font-weight: 500;
  text-align: right;
  color: var(--wg-2);
  font-variant-numeric: tabular-nums;
}

.wg__month {
  margin-bottom: 6px;
  font-size: 12px;
  line-height: 1.2;
  font-weight: 600;
}

.wg-month {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.wg-month__g {
  flex: 1 1 auto;
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  grid-auto-rows: 1fr;
  gap: 1.5px;
}

.wg__last {
  margin-top: 4px;
  font-size: 15px;
  line-height: 1.2;
  font-weight: 600;
}

.wg__vol {
  font-size: 22px;
  line-height: 1.15;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.wg__minis {
  display: flex;
  gap: 12px;
  margin-top: 4px;
}

.wg__minis b {
  display: block;
  font-size: 15px;
  line-height: 1.15;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.wg__minis span {
  display: block;
  font-size: 10px;
  line-height: 1.2;
  font-weight: 500;
  color: var(--wg-2);
}

.hs__search {
  position: absolute;
  left: 50%;
  bottom: 20px;
  display: flex;
  align-items: center;
  gap: 5px;
  height: 30px;
  padding: 0 14px;
  transform: translateX(-50%);
  border-radius: var(--r-pill);
  font-size: 13px;
  font-weight: 500;
  color: #f7f7f8;
  background: rgba(255, 255, 255, 0.2);
}

/* --- Reduced motion: the object stays, and it stops performing ----------------- */

@media (prefers-reduced-motion: reduce) {
  .la__arc,
  .la__big-session,
  .la__big-rest,
  .la__state,
  .la__cap-clock,
  .la__tick,
  .is-resting .la__bar b {
    transition: none;
  }
}

/* Solid corners on the lock-screen pieces when transparency is asked to go. */
@media (prefers-reduced-transparency: reduce) {
  .lk__circle,
  .lk__rect,
  .lk__corner,
  .hs__search {
    background: #2b2c31;
  }
}

/* --- The phones you can use — assets/logger.js ---------------------------------
   The stage takes no pointer of its own, so the words beside the phone can still be
   selected. These are the only things on it that do. */

:where(.phone) button {
  appearance: none;
  border: 0;
  padding: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.phone [hidden] {
  display: none !important;
}

.phone[data-works] button,
.phone[data-works] a {
  pointer-events: auto;
}

.phone[data-works] button:focus-visible,
.phone[data-works] a:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

.la button:focus-visible,
.la a:focus-visible {
  outline-color: #b3c3ff;
}

/* A state change on a small control is its colour, and nothing else moves. */
.srow,
.srow__chk,
.srow .c-in b {
  transition: background-color var(--d-base) var(--ease-out), color var(--d-base) var(--ease-out);
}

.srow__chk {
  position: relative;
}

/* The tick is small on a phone-sized phone: the target is larger than the drawing. */
.srow__chk::before {
  content: '';
  position: absolute;
  inset: -7px;
}

button.exb__add,
button.exb__again,
button.rest__adj,
button.rest__skip,
button.srow__chk {
  transition: background-color var(--d-base) var(--ease-out), color var(--d-base) var(--ease-out);
}

button.exb__add {
  color: var(--text);
}

@media (hover: hover) {
  button.srow__chk:hover,
  button.exb__add:hover,
  button.rest__adj:hover {
    background: var(--surface-pressed);
  }
  .srow--done button.srow__chk:hover {
    background: var(--primary-pressed);
  }
  button.exb__again:hover,
  button.rest__skip:hover {
    background: var(--primary-dim);
  }
}

/* The next thing to press, until somebody has pressed something. */
.hint {
  position: relative;
}

.hint::after {
  content: '';
  position: absolute;
  inset: -5px;
  border-radius: 14px;
  box-shadow: 0 0 0 2px var(--primary);
  pointer-events: none;
  animation: hint 1.8s var(--ease-out) infinite;
}

.la__tick.hint::after {
  border-radius: 50%;
  box-shadow: 0 0 0 2px #b3c3ff;
}

@keyframes hint {
  from {
    opacity: 0.9;
    transform: scale(0.94);
  }
  to {
    opacity: 0;
    transform: scale(1.16);
  }
}

@media (prefers-reduced-motion: reduce) {
  .hint::after {
    animation: none;
  }
}

/* The rest has run out, or been skipped: the pill is not there. */
.rest {
  transition: opacity var(--d-base) var(--ease-out), visibility var(--d-base);
}

.rest--off {
  opacity: 0;
  visibility: hidden;
}

.dev .phone--split .rest.rest--off {
  box-shadow: none;
}

/* Once it is in use the logger is an ordinary screen again: flat, clipped, and its list scrolls
   under the bar, with room at the foot so the rest pill never covers the last row. */
.phone--split.is-together .lg__list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  align-content: start;
  padding-bottom: 116px;
  scrollbar-width: none;
  position: relative;
  pointer-events: auto;
}

.phone--split.is-together .lg__list::-webkit-scrollbar {
  display: none;
}

.dev .phone--split.is-together,
.dev .phone--split.is-together .phone__screen,
.dev .phone--split.is-together .ios,
.dev .phone--split.is-together .lg,
.dev .phone--split.is-together .exb {
  transform-style: flat;
}

.dev .phone--split.is-together .phone__screen,
.dev .phone--split.is-together .ios,
.dev .phone--split.is-together .lg {
  overflow: hidden;
}

.dev .phone--split.is-together .phone__screen {
  background: var(--bg);
}

/* Hand-off on the card: nothing it may tick. */
.la__state--handoff {
  display: flex;
  align-items: center;
  gap: 12px;
}

.la__hand-line {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 15px;
  line-height: 1.25;
  font-weight: 600;
}

.la__hand-hint {
  margin-top: 2px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 13px;
  line-height: 1.25;
  font-weight: 500;
  color: #b7b8bf;
}

.la__open {
  flex: none;
  display: flex;
  align-items: center;
  height: 40px;
  padding: 0 18px;
  border-radius: var(--r-pill);
  background: #b3c3ff;
  color: #131a35;
  font-size: 15px;
  font-weight: 600;
  text-decoration: none;
}

/* The "try it" line, and what it says back. Only with the script, since only then is it true. */
.try {
  display: none;
}

.js .split__copy p.try {
  display: block;
  margin-top: var(--s-lg);
  color: var(--primary);
  font-weight: 600;
}

/* Announcements for a screen reader, out of sight. */
.live {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

/* --- The row of chapters ----------------------------------------------------------
   On a wide window and with scripts off this box is not there as far as layout goes. */

.tour__chapters {
  display: contents;
}

.js3d-narrow .tour__chapters {
  position: relative;
  display: flex;
  align-items: flex-start;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  overscroll-behavior-x: contain;
}

.js3d-narrow .tour__chapters::-webkit-scrollbar {
  display: none;
}

.js3d-narrow .tour__chapters .rule {
  display: none;
}

.js3d-narrow .tour__chapters .section {
  flex: 0 0 100%;
  min-width: 0;
  padding: var(--s-lg) 0 var(--s-3xl);
  scroll-snap-align: center;
  scroll-snap-stop: always;
}

/* The name, the line, the phone, then the words: the phone is in the first view. */
.js3d-narrow .tour__chapters .split,
.js3d-narrow .tour__chapters .split--flip {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0;
}

.js3d-narrow .tour__chapters .split__copy {
  display: contents;
}

.js3d-narrow .tour__chapters .split__copy h2 {
  order: 1;
  text-align: center;
}

.js3d-narrow .tour__chapters .split__copy p.try {
  order: 2;
  margin-top: var(--s-sm);
  text-align: center;
}

.js3d-narrow .tour__chapters .split__art,
.js3d-narrow .tour__chapters .split--flip .split__art {
  order: 3;
  align-self: center;
  margin-top: var(--s-lg);
  animation: none;
  opacity: 1;
}

.js3d-narrow .tour__chapters .split__copy p {
  order: 4;
  margin-top: var(--s-xl);
}

.js3d-narrow .tour__chapters .swipe__more {
  order: 5;
}

.js3d-narrow .tour__chapters .ticks {
  order: 6;
}

.js3d-narrow .tour__chapters .ticks[hidden] {
  display: none;
}

.js3d-narrow .tour__chapters .split__copy .chapter__note {
  order: 7;
}

/* The tabs: the app's pill control, the same card-coloured thumb, and it follows your finger. */
.swipe__tabs {
  position: sticky;
  top: calc(var(--nav-h) + 8px);
  z-index: 5;
  display: flex;
  width: calc(100% - 2 * var(--s-lg));
  max-width: 520px;
  margin: var(--s-xl) auto 0;
  padding: 3px;
  border-radius: var(--r-pill);
  border: 0.5px solid var(--border);
  background: var(--surface-alt);
}

.swipe__thumb {
  position: absolute;
  top: 3px;
  bottom: 3px;
  left: 3px;
  display: block;
  border-radius: var(--r-pill);
  border: 0.5px solid var(--border);
  background: var(--surface);
}

.swipe__tab {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  min-height: 38px;
  padding: 0 2px;
  border: 0;
  border-radius: var(--r-pill);
  background: none;
  color: var(--text-2);
  font: inherit;
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--d-base) var(--ease-out);
  -webkit-tap-highlight-color: transparent;
}

.swipe__tab[aria-current] {
  color: var(--text);
  font-weight: 600;
}

.swipe__more {
  align-self: flex-start;
  min-height: 44px;
  margin-top: var(--s-md);
  padding: 0;
  border: 0;
  background: none;
  color: var(--primary);
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
}

/* --- Everything else, folded away --------------------------------------------------- */

.everything {
  margin-top: clamp(1.5rem, 3vw, 36px);
}

.everything > summary {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 48px;
  padding: 0 22px;
  border-radius: var(--r-pill);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 0.9375rem;
  font-weight: 600;
  cursor: pointer;
  list-style: none;
  transition: background-color var(--d-base) var(--ease-out);
}

.everything > summary::-webkit-details-marker {
  display: none;
}

.everything > summary svg {
  transition: transform var(--d-base) var(--ease-out);
}

.everything[open] > summary svg {
  transform: rotate(180deg);
}

@media (hover: hover) {
  .everything > summary:hover {
    background: var(--surface-pressed);
  }
}

.everything .also {
  margin-top: var(--s-xl);
}

/* --- The ring of filmed lifts — assets/ring.js ---------------------------------------
   Every one of them on a drum. The cards are the white plates the clips are drawn on, so
   the ring reads as the library itself. The edges of the band fade into the page rather
   than stopping, and that is the only thing that dims: nothing here filters per frame. */

.ring {
  --card: 150px;
  margin-top: clamp(28px, 5vw, 56px);
}

.ring__view {
  position: relative;
  height: calc(var(--card) * 1.5);
  overflow: hidden;
  perspective: max(1100px, 86vw);
  touch-action: pan-y;
  cursor: grab;
  user-select: none;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);
}

@media (max-width: 700px) {
  /* Only three cards show on a phone, and a wide fade would wash over the ones at the sides. */
  .ring__view {
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 3%, #000 97%, transparent);
    mask-image: linear-gradient(90deg, transparent, #000 3%, #000 97%, transparent);
  }
}

.ring__view.is-dragging {
  cursor: grabbing;
}

.ring__tilt {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  transform: rotateX(-4deg);
}

.ring__drum {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 0;
  height: 0;
  transform-style: preserve-3d;
  will-change: transform;
}

.ring__card {
  --pop: 0px;
  position: absolute;
  left: calc(var(--card) / -2);
  top: calc(var(--card) / -2);
  display: block;
  width: var(--card);
  height: var(--card);
  padding: 0;
  overflow: hidden;
  border: 0;
  border-radius: 22px;
  background: var(--demo-plate);
  cursor: pointer;
  backface-visibility: hidden;
  transform: rotateY(var(--a)) translateZ(calc(var(--r) + var(--pop)));
  transition: transform var(--d-slow, 0.32s) var(--ease-out);
  -webkit-tap-highlight-color: transparent;
}

.ring__card img {
  display: block;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.ring__card.is-far {
  visibility: hidden;
}

.ring__card.is-front {
  --pop: 34px;
  box-shadow: 0 0 0 3px var(--primary);
}

.ring__card:focus-visible {
  outline: 3px solid var(--primary);
  outline-offset: 3px;
}

.ring__foot {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-md);
  margin-top: var(--s-md);
}

.ring__step {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
  transition: background-color var(--d-base) var(--ease-out);
}

@media (hover: hover) {
  .ring__step:hover {
    background: var(--surface-pressed);
  }
}

.ring__count {
  min-width: 76px;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
}

@media (prefers-reduced-motion: reduce) {
  .ring__card,
  .srow,
  .srow__chk,
  .la__bar b {
    transition: none;
  }
}
