/* =========================================================================
   Dashfit site design system
   =========================================================================

   REGISTER: "Instrument Island in smoked glass", inherited from the app's own
   design law (dashfit-app/docs/DESIGN.md, binding from 2026-09-07). The game
   lives in the world, the mechanics and the motion. The chrome is adult,
   type-led, dark, with real numerals as the hero, one amber accent and smoked
   glass over the world. A marketing site is chrome, so this page is the adult
   half and the gameplay pictures supply all of the light.

   What that means as CSS rules, all of them load-bearing:
     - One ground: --df-ink #0E1A22. There is no fourth ground. No paper, no
       white, no black. color-scheme is dark and there is no light theme.
     - One accent: --df-amber. It fills the primary button, marks the runner
       and fills progress. It never sets a word under 24px, a heading, a link,
       a caption, a ground or a gradient (amber measures 3.14:1).
     - One shadow: --df-shadow. It never animates, never stacks, never takes a
       colour, and there is no hover lift and no second elevation level.
     - Four radii only: 24 card, 32 sheet, 14 tile, capsule. No fifth radius.
     - No gradient of any kind as a fill, no glow, no edge light, no border on
       a surface's own edge, no second fill, no white or light fill.
     - Nothing parallaxes, breathes or pulses. The veil never moves.
     - Every colour and every font-family goes through a custom property.
       There is no bare #fff, white or black outside the :root block below.
     - transform and opacity are the only animated properties. Never
       transition: all.
     - Every interactive target is at least 44 by 44.

   CONTRAST REFERENCE: #F2EFE6 is not a token. It is the brightest world
   sample in the game (sand and sky haze) that the whole contrast law is
   measured against, because the material's blur averages the world and the
   brightest average is the worst case for white text. White on the smoke
   measures 6.18:1 and white 80 percent measures 4.65:1 against it. Every
   figure on this site sits on that case or on something darker.

   FONT RATIONALE: the app's display face is Apple's SF Pro at
   .fontWidth(.compressed). It is licensed for interfaces on Apple platforms
   and cannot be served as a webfont, and the design law names no web
   substitute. Two OFL faces stand in, both self-hosted, both with their
   licence beside them in /assets/fonts/:
     - Anton at weight 900 carries the hero, section heads, numerals and the
       wordmark. It was chosen by eye against the approved App Store artwork,
       because Antonio's heaviest weight is visibly lighter than the app's own
       lettering and Anton matches it.
     - Antonio (variable, 100 to 700) carries card titles at 700 and metric
       values at 600. It is the same design at lighter weights, so the two
       share one family name, "Dashfit Display", and weight alone selects
       between them.
     - Outfit (variable) is the app's own bundled body face and carries every
       word of copy.
   Neither display face ships a tnum table, so tabular figures below are a
   no-op today and are declared only so a future face swap inherits them.
   This substitution is an owner decision parked in x-DevNotes.md. It is a
   one-file swap if the owner picks a different face.

   ========================================================================= */


/* -------------------------------------------------------------------------
   1. Faces
   ------------------------------------------------------------------------- */

@font-face {
  font-family: "Dashfit Display";
  src: url("/assets/fonts/anton-latin.woff2") format("woff2");
  font-weight: 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Dashfit Display";
  src: url("/assets/fonts/antonio-var-latin.woff2") format("woff2");
  font-weight: 100 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Outfit";
  src: url("/assets/fonts/outfit-var-latin.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}


/* -------------------------------------------------------------------------
   2. Tokens
   ------------------------------------------------------------------------- */

:root {
  color-scheme: dark;

  /* The one dark. There is no fourth ground: no paper, no white, no black. */
  --df-ink:            #0E1A22;

  /* The accent and the two statuses. Amber is the only colour the chrome
     uses to mean "yours, now, next". Grass marks a completed state only.
     Coral marks a genuine error only. Sky is a debug hue and is not a token
     on this site at all. */
  --df-amber:          #F6AA1C;
  --df-grass:          #57D08A;
  --df-coral:          #FFA08F;
  --df-on-amber:       #0E1A22;

  /* Type. Nothing lighter than --df-text-secondary ever carries words. */
  --df-text:           #FFFFFF;
  --df-text-secondary: rgba(255, 255, 255, 0.80);

  /* The material. */
  --df-smoke:          rgba(14, 26, 34, 0.66);
  --df-smoke-deep:     rgba(14, 26, 34, 0.84);
  --df-veil-wash:      rgba(14, 26, 34, 0.32);

  /* Lines. */
  --df-hairline:       rgba(255, 255, 255, 0.12);
  --df-track:          rgba(255, 255, 255, 0.16);
  /* The track composited over ink, as a flat colour: what .surface--raised
     already paints. For the one surface that must be opaque and raised at
     once (the open mobile menu), where a translucent track would let the
     page show through. Not a new hue. */
  --df-raised:         #353F46;
  --df-route-walked:   rgba(255, 255, 255, 0.82);
  --df-route-ahead:    rgba(255, 255, 255, 0.40);

  /* An inactive-state mark, never a word a reader needs. It is an alias of
     the route-ahead tone, which is why prefers-contrast lifts both with one
     declaration. */
  --df-dim:            var(--df-route-ahead);

  /* The one shadow. The law's figure is black 28 percent, y 8, blur 24, but
     SwiftUI's shadow radius is a Gaussian standard deviation and CSS blur is
     about twice that, so 48px is the faithful translation of the app's look. */
  --df-shadow-color:   rgba(0, 0, 0, 0.28);
  --df-shadow:         0 8px 48px var(--df-shadow-color);

  /* A picture of a phone, not a ground. Only .device may paint with it. */
  --df-bezel:          #060B10;

  /* Radii. There is no fifth radius. */
  --df-radius-card:    24px;
  --df-radius-sheet:   32px;
  --df-radius-tile:    14px;
  --df-radius-capsule: 999px;

  /* Spacing: the law's 8 / 12 / 18 / 20 / 24 ladder, extended for page
     rhythm. Nothing in the register uses a value off this ladder. */
  --sp-1: 8px;
  --sp-2: 12px;
  --sp-3: 18px;
  --sp-4: 24px;
  --sp-5: 32px;
  --sp-6: 48px;
  --sp-7: 72px;
  --sp-8: 96px;
  --sp-9: 144px;

  --gutter:  20px;   /* the law's floor; widened at 48rem and 72rem */
  --measure: 48ch;   /* about 67 characters of Outfit; ch is the 0 advance */
  --page:    1200px;
  --shelf-h: 64px;

  --font-display: "Dashfit Display", Impact, Haettenschweiler, "Arial Narrow Bold", sans-serif;
  --font-body:    "Outfit", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  /* Motion. The press curve approximates the app's spring (response 0.22,
     damping 0.78). --ease-settle is the law's easeOut. */
  --ease-out:    cubic-bezier(0.2, 0.8, 0.25, 1);
  --ease-settle: cubic-bezier(0, 0, 0.58, 1);
  --dur-press:   220ms;
  --dur-ui:      280ms;
  --dur-settle:  600ms;

  /* The dotted route: a 2px dot every 8px with a round cap, drawn as a hard
     stop rather than a colour ramp. This is how CSS draws a round dot; it is
     not a decorative gradient and carries no second fill. */
  --df-route-dots: radial-gradient(circle at 1px 1px, var(--df-route-ahead) 1px, transparent 1.2px);
}

@media (min-width: 48rem) {
  :root { --gutter: 32px; --shelf-h: 72px; }
}

@media (min-width: 72rem) {
  :root { --gutter: 48px; }
}


/* -------------------------------------------------------------------------
   3. Reset
   ------------------------------------------------------------------------- */

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

html {
  /* Anchor jumps and focus scrolls land below the sticky shelf instead of
     under it. */
  scroll-padding-top: calc(var(--shelf-h) + var(--sp-2));
  /* The island bleeds off the right edge of the hero on purpose. clip keeps
     that from producing a horizontal scrollbar without turning the page into
     a scroll container, which would break the sticky shelf. */
  overflow-x: clip;
  background: var(--df-ink);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  overflow-x: clip;
  /* No background here. html's background is what paints the canvas, and the
     fixed veil layer sits between the canvas and the page content. A
     background on body would cover the veil. */
  margin: 0;
  min-height: 100vh;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 1.0625rem;      /* 17px. The law's body rank is 15pt on a phone
                                held at arm's length; a web page is read
                                further away, so the web rank is 17. */
  line-height: 1.6;
  color: var(--df-text);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4, p, figure, blockquote, dl, dd, ol, ul { margin: 0; }
ol, ul { padding: 0; }

img, video, svg, canvas { display: block; max-width: 100%; }
img, video { height: auto; }

button, input, select, textarea {
  font: inherit;
  color: inherit;
  margin: 0;
}

button { background: none; border: 0; }

a { color: inherit; }

/* Author rules below set display on components that also ship the hidden
   attribute (the media toggle), so hidden has to win. */
[hidden] { display: none !important; }

:focus-visible {
  /* Never transitions. Two pixels, offset, on the one text token, so it
     clears 3:1 on ink and on glass alike. */
  outline: 2px solid var(--df-text);
  outline-offset: 3px;
}

::selection {
  background: var(--df-amber);
  color: var(--df-on-amber);
}

/* Anything an in-page link can land on clears the sticky shelf. */
.band,
.prose h2,
.prose h3,
[data-anchor] {
  scroll-margin-top: calc(var(--shelf-h) + var(--sp-4));
}


/* -------------------------------------------------------------------------
   4. Type ramp
   -------------------------------------------------------------------------
   Headings are sentence case, roman, white. micro is the only uppercase
   rank. Element defaults cover the common case; the .t-* classes are the
   escape hatch for the exceptions and are not a general utility system.
   ------------------------------------------------------------------------- */

.t-hero,
.t-display,
.t-numeral,
.t-title,
.t-metric {
  font-family: var(--font-display);
  color: var(--df-text);
}

.t-hero {
  font-weight: 900;
  font-size: clamp(3.5rem, 8.2vw, 7.25rem);
  line-height: 1.0;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}

.t-display {
  font-weight: 900;
  font-size: clamp(2rem, 4.4vw, 4rem);
  line-height: 1.04;
  letter-spacing: 0;
}

.t-numeral {
  font-weight: 900;
  font-size: clamp(2rem, 3.4vw, 2.75rem);
  line-height: 1;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}

.t-title {
  font-weight: 700;
  font-size: 1.375rem;      /* 22px */
  line-height: 1.15;
  letter-spacing: 0.01em;
}

.t-metric {
  font-weight: 600;
  font-size: 1.25rem;       /* 20px */
  line-height: 1.1;
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
}

.t-lede {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: clamp(1.125rem, 1.5vw, 1.3125rem);
  line-height: 1.5;
  color: var(--df-text-secondary);
}

.t-body {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 1.0625rem;
  line-height: 1.6;
}

.t-body-strong {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 1.0625rem;
  line-height: 1.6;
}

.t-caption {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 0.875rem;      /* 14px */
  line-height: 1.45;
}

.t-micro {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 0.6875rem;     /* 11px */
  line-height: 1.2;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--df-text-secondary);
}

/* Display ranks never clip and never shrink below their rank: a long word
   breaks, a card grows. */
.t-hero,
.t-display,
.t-numeral {
  overflow-wrap: anywhere;
  min-width: 0;
}

h1, h2, h3 { text-wrap: balance; }
p { text-wrap: pretty; }

/* Element defaults inside a band, so a page builder rarely needs a rank
   class at all. Wrapped in :where() so they carry no specificity and any
   .t-* rank class on the same element wins without an !important. */
:where(.band h1) {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(3.5rem, 8.2vw, 7.25rem);
  line-height: 1.0;
  letter-spacing: -0.01em;
  overflow-wrap: anywhere;
  min-width: 0;
}

:where(.band h2) {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(2rem, 4.4vw, 4rem);
  line-height: 1.04;
  overflow-wrap: anywhere;
  min-width: 0;
}

:where(.band h3) {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.375rem;
  line-height: 1.15;
  letter-spacing: 0.01em;
}

/* The one intro paragraph under a section heading. */
.intro {
  max-width: var(--measure);
  font-size: clamp(1.125rem, 1.5vw, 1.3125rem);
  line-height: 1.5;
  color: var(--df-text-secondary);
}

.band h1 + .intro,
.band h2 + .intro { margin-top: var(--sp-3); }

.section-head { margin-bottom: var(--sp-6); }
.section-head > * + * { margin-top: var(--sp-3); }

.muted { color: var(--df-text-secondary); }

@media (min-width: 64rem) {
  .section-head { margin-bottom: var(--sp-7); }
}


/* -------------------------------------------------------------------------
   5. Grounds: the fixed veil, and the two bands
   -------------------------------------------------------------------------
   The app is one place seen at two depths: the sharp island on the Path, the
   blurred island behind every other screen. The site uses exactly that. The
   veil is a pre-blurred picture fixed behind the whole page. It never moves.
   Sections alternate between covering it (ink) and cutting a window in the
   page to show it (veil). That alternation is the only section separator on
   the page: no rules, no fades, no gradients.
   ------------------------------------------------------------------------- */

.veil {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  /* Ink underneath, so a missing or slow picture still lands on the one
     ground rather than on nothing. */
  background: var(--df-ink) url("/assets/img/veil.webp") center / cover no-repeat;
}

.veil::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--df-veil-wash);
}

.band {
  position: relative;
  padding-block: var(--sp-8);
}

/* Opaque ink covers the veil. Type sits directly on it, pictures supply the
   light, rows use hairline dividers. Never draw a smoke card here: on flat
   ink it is invisible. */
.band--ink { background: var(--df-ink); }

/* Transparent, so the veil shows through like a window cut in the page.
   EVERY piece of text in a veil band sits on a glass surface or a chip. */
.band--veil { background: transparent; }

.band--tight { padding-block: var(--sp-7); }

.wrap {
  max-width: var(--page);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

@media (min-width: 64rem) {
  .band { padding-block: var(--sp-9); }
  .band--tight { padding-block: var(--sp-8); }
}


/* -------------------------------------------------------------------------
   6. Layout helpers
   -------------------------------------------------------------------------
   The page builders cannot write CSS, so these three cover the column shapes
   the plan's sections need and nothing more.
   ------------------------------------------------------------------------- */

.cols {
  display: grid;
  gap: var(--sp-4);
}

@media (min-width: 48rem) {
  .cols--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cols--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.split {
  display: grid;
  gap: var(--sp-6);
  align-items: start;
}

@media (min-width: 56rem) {
  .split { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-7); }
  .split--visual-first { grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); }
}

/* Container spacing: the gap between a block's own children. */
.stack > * + * { margin-top: var(--sp-3); }
.stack--tight > * + * { margin-top: var(--sp-1); }
.stack--loose > * + * { margin-top: var(--sp-5); }

/* Element spacing: this block starts a new beat inside its section. */
.spaced { margin-top: var(--sp-5); }
.spaced--tight { margin-top: var(--sp-3); }
.spaced--wide { margin-top: var(--sp-6); }


/* -------------------------------------------------------------------------
   7. Skip link and visually hidden
   ------------------------------------------------------------------------- */

.skip {
  position: fixed;
  top: var(--sp-2);
  left: var(--gutter);
  z-index: 100;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-inline: var(--sp-3);
  border-radius: var(--df-radius-capsule);
  background: var(--df-smoke-deep);
  color: var(--df-text);
  font-weight: 500;
  text-decoration: none;
  transform: translateY(calc(-100% - var(--sp-5)));
}

.skip:focus { transform: none; }

.vh {
  position: absolute !important;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}


/* -------------------------------------------------------------------------
   8. The shelf: sticky header
   -------------------------------------------------------------------------
   The one place on the site that uses a real backdrop-filter, because page
   content passes underneath it. Same material recipe as a surface with the
   corners and the shadow taken off, plus one hairline. On a top bar that
   hairline belongs on the bottom edge, which is where the content meets it.
   ------------------------------------------------------------------------- */

.shelf {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--df-smoke);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  backdrop-filter: blur(20px) saturate(180%);
  border-bottom: 1px solid var(--df-hairline);
}

.shelf__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-1) var(--sp-4);
  min-height: var(--shelf-h);
  padding-block: var(--sp-1);
}

.wordmark {
  /* The approved artwork's wordmark treatment. The word is written in
     natural case in the markup and uppercased here, so a redesign never
     means rewriting copy. The 0.32em tracking leaves a trailing gap after
     the final letter; the mark is always left aligned, so that gap falls on
     the free-space side and needs no correction. */
  margin-inline-end: auto;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 1.125rem;
  line-height: 1.2;
  letter-spacing: 0.32em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--df-text);
  white-space: nowrap;
}

.nav { position: relative; }

.nav__toggle {
  /* Hidden unless site.js has run. Without JS the links simply wrap onto a
     second row of the shelf, which is the no-JS fallback. */
  display: none;
  align-items: center;
  gap: var(--sp-1);
  min-height: 44px;
  min-width: 44px;
  padding-inline: var(--sp-2);
  border-radius: var(--df-radius-capsule);
  background: var(--df-smoke-deep);
  color: var(--df-text);
  font-weight: 500;
  font-size: 0.9375rem;
  cursor: pointer;
}

.nav__bars {
  position: relative;
  width: 16px;
  height: 10px;
  flex: none;
}

.nav__bars::before,
.nav__bars::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 2px;
  border-radius: var(--df-radius-capsule);
  background: currentColor;
}

.nav__bars::before { top: 0; }
.nav__bars::after  { bottom: 0; }

.nav__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 var(--sp-4);
  list-style: none;
}

.nav__link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--df-text-secondary);
  font-weight: 500;
  font-size: 0.9375rem;
  text-decoration: none;
  white-space: nowrap;
}

.nav__link[aria-current="page"] { color: var(--df-text); font-weight: 600; }

@media (hover: hover) and (pointer: fine) {
  .nav__link:hover { color: var(--df-text); }
}

@media (max-width: 55.999rem) {
  /* .js lands on <html> as soon as site.js runs. */
  .js .nav__toggle { display: inline-flex; }

  .js .nav__panel {
    position: absolute;
    /* The shelf's inner wrap is the containing block, so the panel spans the
       page's own content edges rather than hanging off the toggle. */
    inset-inline: var(--gutter);
    top: calc(100% + var(--sp-2));
    display: none;
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--df-radius-card);
    /* Opaque and raised, not the smoke. The panel is a descendant of the
       shelf, and an ancestor's backdrop-filter makes it a backdrop root, so
       a second backdrop-filter inside it samples nothing and blurs nothing.
       A smoke fill with no working blur under it leaves the page's own words
       legible through the menu, and plain ink has no edge at all against an
       ink band. The raised composite is the fill the system already uses for
       a surface an ink band would swallow. */
    background: var(--df-raised);
    box-shadow: var(--df-shadow);
  }

  .js .shelf__inner { position: relative; }
  .js .nav { position: static; }

  /* The two bars fold into a cross while the menu is open. */
  .nav__bars::before,
  .nav__bars::after { transition: transform var(--dur-ui) var(--ease-out); }
  .nav__toggle[aria-expanded="true"] .nav__bars::before { transform: translateY(4px) rotate(45deg); }
  .nav__toggle[aria-expanded="true"] .nav__bars::after  { transform: translateY(-4px) rotate(-45deg); }

  .js .nav[data-open="true"] .nav__panel { display: block; }

  .js .nav__list {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
  }

  /* display: flex, not the inline-flex the wide layout uses. The list items
     stretch to the panel's width, but an inline-flex anchor inside one still
     shrinks to its own text, so the divider below only spanned the width of
     each label and the rules came out ragged. A block-level anchor fills the
     row, which squares the dividers and makes the whole row the tap target. */
  .js .nav__link { display: flex; min-height: 48px; }
  .js .nav__list > li + li .nav__link { border-top: 1px solid var(--df-hairline); }
}



/* -------------------------------------------------------------------------
   9. Buttons and the pre-launch status
   -------------------------------------------------------------------------
   One primary per page view. Labels never wrap to a truncated stub: a
   control whose name stops before it says where it goes is a defect.
   ------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-1);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.375rem;
  line-height: 1.15;
  letter-spacing: 0.01em;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  border-radius: var(--df-radius-capsule);
  /* transform only. A pressed surface deepens and settles; it never lifts,
     and the shadow never animates. */
  transition: transform var(--dur-press) var(--ease-out),
              opacity var(--dur-press) var(--ease-out);
}

.btn--primary {
  min-height: 56px;
  padding-inline: var(--sp-4);
  background: var(--df-amber);
  color: var(--df-on-amber);
  box-shadow: var(--df-shadow);
}

.btn--primary:active {
  transform: scale(0.98);
  opacity: 0.88;
}

.btn--primary:focus-visible {
  /* An ink ring directly on the capsule, then the white ring outside it, so
     the indicator clears 3:1 whether the button sits on ink or on glass. The
     elevation shadow steps aside while the rings are drawn rather than
     stacking a second shadow underneath them. */
  outline: 2px solid var(--df-text);
  outline-offset: 2px;
  box-shadow: 0 0 0 2px var(--df-ink);
}

.btn--secondary {
  min-height: 48px;
  padding-inline: var(--sp-4);
  background: var(--df-smoke);
  color: var(--df-text);
  box-shadow: var(--df-shadow);
}

.btn--secondary:active {
  background: var(--df-smoke-deep);
  transform: scale(0.98);
  opacity: 0.82;
}

.btn--text {
  min-height: 44px;
  padding-inline: 0;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 1.0625rem;
  letter-spacing: 0;
  color: var(--df-text-secondary);
  box-shadow: none;
  /* A link that looks like its neighbouring prose is not a link. The line
     is the rest state; hover and focus thicken it. */
  text-decoration: underline;
  text-decoration-color: currentColor;
  text-underline-offset: 0.22em;
  text-decoration-thickness: 1px;
}

.btn--text:active { opacity: 0.82; }

@media (hover: hover) and (pointer: fine) {
  .btn--text:hover {
    color: var(--df-text);
    text-decoration-thickness: 2px;
  }
  .btn--primary:hover,
  .btn--secondary:hover { filter: brightness(1.08); }
}

.btn--text:focus-visible { color: var(--df-text); text-decoration-thickness: 2px; }

.btn--small {
  min-height: 44px;
  padding-inline: var(--sp-3);
  font-size: 1.0625rem;
}

.btn--block { display: flex; width: 100%; }

.btn[disabled],
.btn[aria-disabled="true"] {
  opacity: 0.4;
  cursor: default;
  pointer-events: none;
}

/* The pre-launch store status. A span, never a link, never a badge, and no
   capsule either: a filled pill in the action row read as the control while
   the real link beside it read as prose. Plain secondary text, the same on
   ink and on the veil. The 44px line keeps the action row's baseline. */
.status {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--df-text-secondary);
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 0.9375rem;
  line-height: 1.3;
  cursor: default;
}

.actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
}


/* -------------------------------------------------------------------------
   10. Surfaces, chips
   -------------------------------------------------------------------------
   The one recipe: the smoke fill, the one shadow, a radius from the set.
   There is no step zero: no edge light, no hairline on a surface's own edge.
   Cards live only in veil bands. Over the veil the surface skips
   backdrop-filter, because the veil picture is already blurred and the smoke
   fill does the legibility work.
   ------------------------------------------------------------------------- */

.surface {
  position: relative;
  padding: var(--sp-4);
  border-radius: var(--df-radius-card);
  background: var(--df-smoke);
  box-shadow: var(--df-shadow);
}

.surface--sheet {
  padding: var(--sp-5);
  border-radius: var(--df-radius-sheet);
}

.surface--tile {
  padding: var(--sp-3);
  border-radius: var(--df-radius-tile);
  box-shadow: none;
}

/* Anything sitting over a sharp picture or a playing clip. */
.surface--deep { background: var(--df-smoke-deep); }

/* A quiet raised panel on an ink band, where a smoke card would be
   invisible. Used by the legal callouts. */
.surface--raised {
  background: var(--df-track);
  border-radius: var(--df-radius-tile);
  box-shadow: none;
}

@media (min-width: 64rem) {
  .surface--sheet { padding: var(--sp-6); }
}

/* A fact pair: the value over its label, the app's GlassStat gap of 2.
   The value is title or numeral rank; the label is the micro rank, which is
   the one place on this site that micro belongs. */
.stat > * + * { margin-top: 2px; }

.stat__value {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.375rem;
  line-height: 1.15;
  letter-spacing: 0.01em;
  color: var(--df-text);
}

.stat__label {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 0.6875rem;
  line-height: 1.2;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--df-text-secondary);
}

/* The only way text may sit over a sharp picture. Its default rank is
   metric; add .t-title or .t-caption for the other two cases. The type
   block is in :where() so a rank class on the same element wins. */
.chip {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  padding: 6px 14px;
  border-radius: var(--df-radius-capsule);
  background: var(--df-smoke-deep);
  color: var(--df-text);
}

:where(.chip) {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.25rem;
  line-height: 1.1;
  letter-spacing: 0.01em;
}


/* -------------------------------------------------------------------------
   11. Pictures and the drawn device
   ------------------------------------------------------------------------- */

.picture {
  position: relative;
  overflow: hidden;
  border-radius: var(--df-radius-card);
  background: var(--df-ink);
}

.picture > img {
  width: 100%;
  height: auto;
}

.picture__chip {
  position: absolute;
  left: var(--sp-2);
  bottom: var(--sp-2);
  max-width: calc(100% - var(--sp-4));
}

.picture--tile { border-radius: var(--df-radius-tile); }

/* A picture of a phone, mirroring the approved App Store artwork's own thin
   dark frame. Kept quiet: no chrome highlight, no reflection, no gradient.
   The radius is a percentage pair so the corner stays circular at every
   width: 13 percent of the width horizontally, and the same distance
   expressed against the height vertically (1206 / 2622 = 0.46). */
.device {
  position: relative;
  width: 100%;
  max-width: 280px;
  margin-inline: auto;
  aspect-ratio: 1206 / 2622;
  padding: 8px;
  border-radius: 13% / 5.98%;
  background: var(--df-bezel);
  box-shadow: var(--df-shadow);
}

.device--lg { max-width: 340px; }
.device--sm { max-width: 220px; }

.device__screen {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Concentric with the bezel: the outer radius minus the 8px frame,
     re-expressed against the smaller inner box. */
  border-radius: 11.2% / 5.1%;
  background: var(--df-ink);
}

.device__island {
  position: absolute;
  top: 1.9%;
  left: 50%;
  translate: -50% 0;
  width: 30%;
  aspect-ratio: 125 / 36;
  border-radius: var(--df-radius-capsule);
  background: var(--df-bezel);
  pointer-events: none;
}

.device-figure { margin: 0; }
.device-figure > figcaption { margin-top: var(--sp-2); }

/* Optional pause control for an autoplaying clip. Silent looping media still
   needs a way to stop it. */
.media-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  min-width: 44px;
  padding-inline: var(--sp-3);
  border-radius: var(--df-radius-capsule);
  background: var(--df-track);
  color: var(--df-text-secondary);
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 0.875rem;
  cursor: pointer;
}

@media (hover: hover) and (pointer: fine) {
  .media-toggle:hover { color: var(--df-text); }
}


/* -------------------------------------------------------------------------
   12. Steps: the Path route as an ordered list
   -------------------------------------------------------------------------
   A real sequence. The numerals are the markers and a dotted route connects
   them, which is the app's Path route and is what encodes the order.
   ------------------------------------------------------------------------- */

.steps {
  --steps-marker: 3rem;
  --steps-gap: var(--sp-5);
  display: grid;
  gap: var(--steps-gap);
  list-style: none;
  counter-reset: df-step;
}

.steps__item {
  position: relative;
  display: grid;
  grid-template-columns: var(--steps-marker) minmax(0, 1fr);
  column-gap: var(--sp-2);
  align-items: start;
  /* The item is stretched to the tallest column by the outer grid. Without
     this, its own auto rows share that extra height and the three titles
     land at three different heights. */
  align-content: start;
}

.steps__marker {
  display: flex;
  align-items: center;
  height: var(--steps-marker);
  font-family: var(--font-display);
  font-weight: 900;
  justify-content: center;
  font-size: clamp(2rem, 3.4vw, 2.75rem);
  line-height: 1;
  color: var(--df-text-secondary);
  font-variant-numeric: tabular-nums;
}

.steps__body > * + * { margin-top: var(--sp-1); }
.steps__body p { color: var(--df-text-secondary); }

.steps__title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.375rem;
  line-height: 1.15;
  letter-spacing: 0.01em;
  padding-top: calc((var(--steps-marker) - 1.375rem * 1.15) / 2);
}

/* The route, drawn only between markers and never past the last one. */
.steps__item:not(:last-child)::after {
  content: "";
  position: absolute;
  left: calc(var(--steps-marker) / 2 - 1px);
  top: calc(var(--steps-marker) + 6px);
  bottom: calc(-1 * var(--steps-gap) + 2px);
  width: 2px;
  background-image: var(--df-route-dots);
  background-size: 2px 8px;
  background-repeat: repeat-y;
}

/* Three across, with the route running horizontally between the numerals.
   This is an opt-in, not a breakpoint on .steps itself: the layout depends
   on how wide the steps' own container is, and a viewport media query
   cannot see that. Use .steps--row only where the list gets a full-width
   column; leave it off inside a half-width card. */
@media (min-width: 48rem) {
  .steps--row {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    column-gap: var(--steps-gap);
  }

  .steps--row .steps__item {
    grid-template-columns: minmax(0, 1fr);
    row-gap: var(--sp-2);
  }

  .steps--row .steps__title { padding-top: 0; }
  .steps--row .steps__marker { justify-content: flex-start; }

  .steps--row .steps__item:not(:last-child)::after {
    left: var(--sp-5);
    right: calc(-1 * var(--steps-gap));
    top: calc(var(--steps-marker) / 2 - 1px);
    bottom: auto;
    width: auto;
    height: 2px;
    background-size: 8px 2px;
    background-repeat: repeat-x;
  }
}


/* -------------------------------------------------------------------------
   13. Rows: a hairline-divided list
   -------------------------------------------------------------------------
   The divider sits between rows only and never on the list's outer edge.
   It is decorative, so it is drawn and never announced.
   ------------------------------------------------------------------------- */

.rows { list-style: none; }

.row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 2px var(--sp-4);
  align-items: start;
  min-height: 52px;
  padding-block: var(--sp-2);
}

.row + .row { border-top: 1px solid var(--df-hairline); }

.row__title {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 1.0625rem;
  line-height: 1.5;
  color: var(--df-text);
}

.row__detail {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 1.0625rem;
  line-height: 1.6;
  color: var(--df-text-secondary);
  margin: 0;
}

.row__metric {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.25rem;
  line-height: 1.1;
  letter-spacing: 0.01em;
  color: var(--df-text);
  font-variant-numeric: tabular-nums;
}

/* The price is the row's most read figure and carries the numeral rank. The
   component rule above would otherwise outrank the utility. */
.row--metric .row__metric {
  font-weight: 900;
  font-size: clamp(2rem, 3.4vw, 2.75rem);
  line-height: 1;
  letter-spacing: 0;
}

/* A row whose trailing figure sits beside its text from 40rem up. The third
   track takes the slack, so the price sits after the plan instead of at the
   far edge of the card, and the hairline still runs the full width. */
@media (min-width: 40rem) {
  .row--metric {
    grid-template-columns: minmax(0, 34rem) auto minmax(0, 1fr);
    column-gap: var(--sp-6);
  }
  .row--metric .row__title  { grid-column: 1; }
  .row--metric .row__metric { grid-column: 2; grid-row: 1 / span 2; align-self: center; }
  .row--metric .row__detail { grid-column: 1; }
}

/* A row whose title and detail sit in two columns. */
@media (min-width: 48rem) {
  .rows--split .row {
    grid-template-columns: minmax(9rem, 0.2fr) minmax(0, 1fr);
    align-items: start;
  }
  .rows--split .row__detail { grid-row: 1; grid-column: 2; }
}


/* -------------------------------------------------------------------------
   14. The rail: a horizontal snap strip
   -------------------------------------------------------------------------
   Works with no JS. The scrollbar is the drag affordance, so it stays
   visible, and the strip is focusable so a keyboard can scroll it.
   ------------------------------------------------------------------------- */

.rail {
  display: flex;
  gap: var(--sp-3);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: var(--gutter);
  padding-inline: var(--gutter);
  margin-inline: calc(-1 * var(--gutter));
  padding-bottom: var(--sp-2);
  scrollbar-width: thin;
  scrollbar-color: var(--df-track) transparent;
}

.rail__item {
  flex: 0 0 auto;
  width: min(78vw, 320px);
  scroll-snap-align: start;
}

.rail__item > * + * { margin-top: 2px; }
.rail__item > .picture + * { margin-top: var(--sp-2); }

.rail:focus-visible {
  outline: 2px solid var(--df-text);
  outline-offset: 4px;
}


/* -------------------------------------------------------------------------
   15. FAQ
   ------------------------------------------------------------------------- */

.faq { list-style: none; }

.faq__item { border-top: 1px solid var(--df-hairline); }
.faq__item:first-child { border-top: 0; }

.faq__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  min-height: 56px;
  padding-block: var(--sp-2);
  list-style: none;
  cursor: pointer;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 1.0625rem;
  line-height: 1.5;
  color: var(--df-text);
}

.faq__summary::-webkit-details-marker { display: none; }

.faq__summary:focus-visible {
  outline: 2px solid var(--df-text);
  outline-offset: -2px;
}

/* A plus drawn in CSS that turns into a cross on open. transform only. */
.faq__mark {
  position: relative;
  flex: none;
  width: 16px;
  height: 16px;
  color: var(--df-text-secondary);
  transition: transform var(--dur-ui) var(--ease-out);
}

.faq__mark::before,
.faq__mark::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 7px;
  height: 2px;
  border-radius: var(--df-radius-capsule);
  background: currentColor;
}

.faq__mark::after { transform: rotate(90deg); }

.faq__item[open] .faq__mark { transform: rotate(45deg); }

.faq__answer {
  padding-bottom: var(--sp-4);
  max-width: var(--measure);
  color: var(--df-text-secondary);
}

.faq__answer > * + * { margin-top: var(--sp-2); }

/* Progressive enhancement: where the browser can interpolate to a keyword,
   the panel opens over one UI beat. Without support it simply opens. */
@media (prefers-reduced-motion: no-preference) {
  @supports (interpolate-size: allow-keywords) and selector(::details-content) {
    :root { interpolate-size: allow-keywords; }

    .faq__item::details-content {
      block-size: 0;
      overflow: clip;
      transition: block-size var(--dur-ui) var(--ease-out),
                  content-visibility var(--dur-ui) var(--ease-out) allow-discrete;
    }

    .faq__item[open]::details-content { block-size: auto; }
  }
}


/* -------------------------------------------------------------------------
   16. Prose and the legal page layout
   ------------------------------------------------------------------------- */

.prose {
  max-width: var(--measure);
  color: var(--df-text-secondary);
}

.prose > * + * { margin-top: var(--sp-3); }

.prose h2 {
  margin-top: var(--sp-6);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.375rem;
  line-height: 1.15;
  letter-spacing: 0.01em;
  color: var(--df-text);
}

.prose h3 {
  margin-top: var(--sp-5);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.25rem;
  line-height: 1.2;
  letter-spacing: 0.01em;
  color: var(--df-text);
}

.prose ul,
.prose ol {
  padding-inline-start: 1.25em;
}

.prose li + li { margin-top: var(--sp-1); }

.prose strong { font-weight: 500; color: var(--df-text); }

/* A link inside a block of text is underlined, because colour alone is not a
   cue. A standalone action uses .btn--text instead. */
.prose a {
  color: var(--df-text);
  text-decoration: underline;
  text-underline-offset: 0.22em;
  text-decoration-thickness: 1px;
  text-decoration-skip-ink: auto;
}

.prose a:hover { text-decoration-thickness: 2px; }

/* A callout on an ink band, where a smoke card would be invisible. */
.prose .callout {
  padding: var(--sp-3);
  border-radius: var(--df-radius-tile);
  background: var(--df-track);
  color: var(--df-text);
}

.legal {
  display: grid;
  gap: var(--sp-6);
}

.legal__eyebrow { color: var(--df-text-secondary); }

.legal__toc { min-width: 0; }

/* Sentence case at caption rank. micro is reserved for a stat label, and
   this is a list heading, not a stat. */
.legal__toc h2 {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 0.875rem;
  line-height: 1.45;
  color: var(--df-text-secondary);
  margin-bottom: var(--sp-2);
}

.toc__list { list-style: none; }

.toc__link {
  display: flex;
  align-items: center;
  min-height: 44px;
  font-size: 0.9375rem;
  line-height: 1.4;
  color: var(--df-text-secondary);
  text-decoration: underline;
  text-decoration-color: currentColor;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.22em;
}

.toc__list > li + li .toc__link { border-top: 1px solid var(--df-hairline); }

@media (hover: hover) and (pointer: fine) {
  .toc__link:hover { color: var(--df-text); text-decoration-thickness: 2px; }
}

.toc__link:focus-visible { color: var(--df-text); text-decoration-thickness: 2px; }

@media (min-width: 64rem) {
  .legal {
    grid-template-columns: 15rem minmax(0, 1fr);
    gap: var(--sp-7);
    align-items: start;
  }

  .legal__toc {
    position: sticky;
    top: calc(var(--shelf-h) + var(--sp-4));
    max-height: calc(100vh - var(--shelf-h) - var(--sp-6));
    overflow-y: auto;
  }
}


/* -------------------------------------------------------------------------
   17. Footer
   ------------------------------------------------------------------------- */

.footer {
  background: var(--df-ink);
  padding-block: var(--sp-7) var(--sp-5);
}

.footer__cols {
  display: grid;
  gap: var(--sp-6);
}

@media (min-width: 48rem) {
  .footer__cols { grid-template-columns: minmax(0, 1.3fr) repeat(2, minmax(0, 1fr)); }
}

.footer__heading {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.375rem;
  line-height: 1.15;
  letter-spacing: 0.01em;
  color: var(--df-text);
  margin-bottom: var(--sp-2);
}

.footer__blurb { color: var(--df-text-secondary); }
.footer__blurb + .footer__blurb { margin-top: var(--sp-1); }

.footer__links { list-style: none; }

.footer__link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--df-text-secondary);
  text-decoration: none;
  font-size: 1.0625rem;
}

@media (hover: hover) and (pointer: fine) {
  .footer__link:hover { color: var(--df-text); text-decoration: underline; }
}

.footer__bar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-4);
  justify-content: space-between;
  margin-top: var(--sp-6);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--df-hairline);
  color: var(--df-text-secondary);
  font-size: 0.875rem;
  line-height: 1.45;
}


/* -------------------------------------------------------------------------
   18. The two sanctioned reveals
   -------------------------------------------------------------------------
   The law sanctions exactly one hero reveal, scale 0.96 to 1, once. The
   default state is fully visible, so nothing is ever hidden without the
   animation, and neither rule exists at all under reduced motion.
   There is no scroll-reveal anywhere on this site.
   ------------------------------------------------------------------------- */

@media (prefers-reduced-motion: no-preference) {
  .settle {
    animation: df-settle var(--dur-settle) var(--ease-settle) both;
  }

  .settle--fade {
    animation: df-fade var(--dur-ui) var(--ease-settle) both;
  }
}

@keyframes df-settle {
  from { opacity: 0; transform: scale(0.96); }
  to   { opacity: 1; transform: none; }
}

@keyframes df-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}


/* -------------------------------------------------------------------------
   19. Preference media queries
   ------------------------------------------------------------------------- */

/* Increase Contrast lifts exactly three tokens. Nothing else moves.
   --df-dim is an alias of the route-ahead tone, so it follows the third. */
@media (prefers-contrast: more) {
  :root {
    --df-text-secondary: rgba(255, 255, 255, 0.88);
    --df-hairline:       rgba(255, 255, 255, 0.24);
    --df-track:          rgba(255, 255, 255, 0.34);
    --df-route-ahead:    rgba(255, 255, 255, 0.60);
  }
}

/* Reduce Transparency replaces the material and the smoke with opaque ink
   and keeps everything else. The veil picture goes with it, because there is
   no blurred picture under chrome in that mode. */
@media (prefers-reduced-transparency: reduce) {
  .shelf,
  .nav__toggle,
  .surface,
  .surface--deep,
  .chip,
  .btn--secondary,
  .status,
  .skip,
  .media-toggle {
    background-color: var(--df-ink);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }

  .veil { background-image: none; }
  .veil::after { background: var(--df-ink); }
}

/* Reduce Motion resolves every beat immediately. Lane moves apply as a
   single state change, the jump and the squat land already finished, and no
   clip autoplays (site.js owns that half). Nothing is left invisible,
   because every animated element's default state is its final state. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 1ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }
}


/* -------------------------------------------------------------------------
   20. Print
   -------------------------------------------------------------------------
   The only place the site is not dark. White paper is fine for print, and a
   legal page is the one thing anybody prints.
   ------------------------------------------------------------------------- */

@media print {
  /* The print token block. Every colour below still goes through a custom
     property; these four are the paper equivalents of the screen tokens. */
  :root {
    --df-paper:          #FFFFFF;
    --df-print-ink:      #000000;
    --df-ink:            var(--df-paper);
    --df-text:           var(--df-print-ink);
    --df-text-secondary: #1A1A1A;
    --df-hairline:       #CCCCCC;
    --df-track:          #F0F0F0;
    --df-shadow:         none;
    --df-smoke:          var(--df-paper);
    --df-smoke-deep:     var(--df-paper);
    --df-veil-wash:      var(--df-paper);
    color-scheme: light;
  }

  html, body {
    background: var(--df-paper);
    color: var(--df-print-ink);
    font-size: 11pt;
  }

  .veil,
  .shelf,
  .skip,
  .legal__toc,
  .footer__links,
  .media-toggle { display: none !important; }

  .band,
  .band--ink,
  .band--veil {
    background: var(--df-paper);
    padding-block: 0;
  }

  .legal { display: block; }
  .prose { max-width: none; color: var(--df-print-ink); }
  .prose a { color: var(--df-print-ink); }
  .prose a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 9pt; }
  .prose .callout { background: var(--df-track); }

  .surface,
  .surface--sheet,
  .surface--tile,
  .surface--deep {
    background: var(--df-paper);
    box-shadow: none;
    padding: 0;
  }

  .faq__item[open]::details-content { block-size: auto; }
  .prose h2, .prose h3 { break-after: avoid; }
  p, li { break-inside: avoid; }
}


/* === home page additions === */
/* -------------------------------------------------------------------------
   Everything above this line is the shared design system, written by the
   foundation builder. Only the home page uses what follows, and it exists
   for one reason: the hero is the page's single rule break, where the
   island is larger than its column and bleeds off the trailing edge of the
   viewport with the phone in front of it. No component above draws that.

   Nothing here introduces a colour, a radius, a shadow or a font: every
   value is a token or a measurement taken from the shipped type.
   ------------------------------------------------------------------------- */

.band--hero {
  /* Two measurements drive the island.

     --hero-island is the drawn width of the picture. On a phone it is wider
     than the viewport, so the island is cropped on both sides and reads as
     a window onto something bigger; above 64rem it is a fixed band of
     widths that keeps the island taller than the phone in front of it
     without ever growing so tall that it reaches the band's own edges.

     --hero-bleed is the distance from the content column's trailing edge
     out to the viewport's trailing edge: the gutter, plus whatever margin
     the 1200px page leaves once the window is wider than that. Anchoring
     the island past that distance is what makes it bleed off the page.
     100vw counts the scrollbar, so the real bleed is a few pixels more
     than asked for, which is harmless in the one direction it errs. */
  --hero-island: min(130vw, 34rem);
  --hero-bleed: calc(var(--gutter) + max(0px, (100vw - var(--page)) / 2));

  /* A guard, not a layout tool. The island is sized so it never reaches the
     band's own edges; this keeps a future change to those numbers from
     spilling a sharp picture into the veil band below. */
  overflow: clip;
}

@media (min-width: 64rem) {
  .band--hero {
    /* Wide enough that its left edge stays behind the phone, which is
       pinned to the grid while the island is pinned to the viewport. */
    --hero-island: clamp(28rem, calc(50vw - 12rem), 60rem);

    /* The fold is the hero's job: at 1280 by 800 the headline, the lede,
       the button and the store status all have to be readable without
       scrolling. The band's 144px rhythm at this width would spend 288px of
       an 800px viewport on air, so the hero keeps the 96px step at the top
       and takes the taller step at the bottom, where the island needs the
       room and nothing needs to be read. */
    padding-block: var(--sp-8) var(--sp-9);
  }
}

.hero__grid {
  display: grid;
  gap: var(--sp-6);
  align-items: center;
}

@media (min-width: 64rem) {
  .hero__grid {
    /* The text column is sized off a measurement, not a ratio. "Your body is
       the" sets 6.31em wide in Anton, so at the hero rank's 116px ceiling
       the headline needs 731px to hold to two lines. This track resolves to
       791px inside the 1200px page and never drops below 656px at 64rem,
       where the rank has shrunk to 84px and needs 530px. The trailing track
       is the phone: 256 to 320px, which is the drawn device's own range. */
    grid-template-columns: minmax(0, 1fr) clamp(16rem, 24%, 20rem);
  }
}

/* The proof line is a caption, so it keeps a caption's measure rather than
   running the full width of the headline column. */
.hero__proof { max-width: 46ch; }

.hero__visual {
  position: relative;
  display: grid;
  justify-items: center;
  align-content: center;
  /* Stacked under the copy, the island still has to be contained: it is
     absolutely positioned, so without a floor here it would ride up over
     the proof line above it. */
  min-height: calc(var(--hero-island) * 1.5);
}

.hero__island {
  position: absolute;
  z-index: 0;
  display: block;
  width: var(--hero-island);
  /* The island is deliberately wider than its box; the reset's 100% cap
     would undo that. */
  max-width: none;
  /* Logical, so a right-to-left document positions it from the same edge of
     the column rather than the same side of the screen. */
  inset-inline-start: 50%;
  top: 50%;
  translate: -50% -50%;
}

.hero__island img { width: 100%; }

/* The phone stands in front of the island, which is the whole composition. */
.hero__device {
  position: relative;
  z-index: 1;
}

@media (min-width: 64rem) {
  .hero__visual {
    /* Beside the copy the phone sets the height and the island overhangs it
       top and bottom, inside the band's own padding. */
    min-height: 0;
  }

  .hero__island {
    inset-inline-start: auto;
    translate: 0 -50%;
    inset-inline-end: calc(-1 * (var(--hero-bleed) + var(--sp-6)));
  }
}

/* A <picture> between .device and its screen breaks the image's height:
   100%, because the picture itself has no height to resolve against. Every
   capture on this page carries an AVIF source, so every one needs it. */
.device > picture {
  display: block;
  width: 100%;
  height: 100%;
}

/* A secondary button on an ink band. .btn--secondary fills with the smoke,
   which is ink at 66 percent: over the veil it reads, over flat ink it is ink
   on ink and the control disappears, leaving a bare word where a button
   belongs. The system already names the answer for a surface an ink band
   would swallow: the raised fill that .surface--raised uses. The white label
   is what carries the meaning either way. The press still deepens, because
   the base rule here would otherwise outrank the pressed state.

   No ink band carries a secondary button today. scripts/go-live.mjs demotes
   the hero's primary to .btn--secondary on launch day, and the hero is an
   ink band, so without this rule the site would ship an invisible "See how
   it works" button the first time it went live. Verified against the
   launched copy, not just the pre-launch page. */
.band--ink .btn--secondary {
  background: var(--df-track);
}

.band--ink .btn--secondary:active {
  background: var(--df-hairline);
}


/* -------------------------------------------------------------------------
   21. Review round one, 2026-09-21
   -------------------------------------------------------------------------
   Additive rules from the first post-launch review. Each is a layout or
   accessibility correction inside the existing token set. */

/* The skip link hands focus to main so the next Tab starts inside it.
   Chrome treats that programmatic move as keyboard focus and would draw the
   ring around the whole page. */
main:focus-visible { outline: 0; }

/* Running text that had no cap. The reading measure applies to every
   paragraph that sits directly on a surface, and to row details. */
.row__detail,
.surface > p.t-body,
.surface > p.t-caption,
.surface--sheet > p { max-width: var(--measure); }

/* A veil band's heading plate ends where its words end. */
.band--veil .section-head.surface {
  width: fit-content;
  max-width: 100%;
}

/* Two facts read as a pair, not as two corners of a card. */
@media (min-width: 40rem) {
  .cols--pair {
    grid-template-columns: repeat(2, max-content);
    column-gap: var(--sp-7);
    justify-content: start;
  }
}

/* A picture beside a phone: the phone column is the phone's own width. */
@media (min-width: 56rem) {
  .split--device { grid-template-columns: minmax(0, 1fr) auto; }
}

/* A phone under a left-aligned caption shares the caption's edge. */
@media (min-width: 48rem) {
  .device-figure .device { margin-inline: 0; }
}

/* A row of phone captures becomes a swipe rail on phones, one screen per
   idea instead of four stacked screens of near-identical artwork. */
@media (max-width: 47.999rem) {
  /* The rail bleeds to the surface's own edges (its padding is --sp-4) and
     each screen is a little narrower than the row, so the next one peeks
     in and says the row moves. */
  .cols--rail {
    display: flex;
    gap: var(--sp-3);
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
    scroll-padding-inline: var(--sp-4);
    margin-inline: calc(-1 * var(--sp-4));
    padding-inline: var(--sp-4);
    padding-bottom: var(--sp-2);
    scrollbar-width: thin;
    scrollbar-color: var(--df-track) transparent;
  }
  .cols--rail > * {
    flex: 0 0 min(66vw, 17.5rem);
    scroll-snap-align: start;
  }
  .cols--rail:focus-visible {
    outline: 2px solid var(--df-text);
    outline-offset: 4px;
  }
}

/* Eight courses show as eight from 64rem up: a four by two grid inside the
   wrap. The rail with its peek stays for narrower screens. */
.rail { scroll-snap-type: x proximity; }

@media (min-width: 64rem) {
  .rail {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--sp-4);
    overflow: visible;
    margin-inline: 0;
    padding-inline: 0;
    padding-bottom: 0;
    scroll-snap-type: none;
  }
  .rail__item { width: auto; }
}

/* Windows High Contrast and other forced palettes strip fills and shadows,
   which is where every capsule and glass edge on this page lives. Each
   control and surface gets a system-coloured edge, and the veil picture
   gives way to the system canvas. Nothing here renders outside the
   query. */
@media (forced-colors: active) {
  .btn--primary,
  .btn--secondary,
  .nav__toggle,
  .media-toggle,
  .chip,
  .surface { border: 1px solid currentColor; }
  .btn--primary { border-color: LinkText; }
  @media (max-width: 55.999rem) {
    .js .nav__panel { border: 1px solid currentColor; }
  }
  .veil { background-image: none; }
  .veil::after { background: Canvas; }
}
