/* PRsonal — the web face of the app's "Graphite" design system.
 *
 * Every colour, size, radius and duration below is lifted verbatim from
 * prsonal/prsonal_app/specs/design_system.md, which is the app's single source
 * of truth. Where that spec names a token, this file uses the same name, so a
 * palette change there is a find-and-replace here rather than a redesign.
 *
 * THE FOUR TENETS, and each one is a rule this stylesheet actually enforces:
 *   1. Flat over boxed   - hierarchy comes from type, spacing and ONE hairline.
 *                          There is no card with a fill and a border on this
 *                          page except the two the spec still allows (the
 *                          phone mock's live row, and the download panel).
 *   2. Hard-edged        - --radius-panel is 0 and is used everywhere. Only
 *                          genuine pills get --radius-full.
 *   3. Data stability    - every number is `mono` + tabular-nums, so a version
 *                          string changing from 1.0.9 to 1.0.10 does not
 *                          reflow the line it sits on.
 *   4. Colour economy    - accent is CHALK, not a hue. success/danger/warning
 *                          appear only where they mean something.
 *
 * DARK ONLY. The app has no light theme, so neither does this: there is no
 * prefers-color-scheme block and `color-scheme: dark` tells the browser to
 * stop offering. A light variant would be a different product.
 *
 * NO WEBFONT REQUEST. The spec says the app uses Roboto, which IS the Android
 * system font - so on the devices this page is selling to, the stack below
 * resolves to the exact face the screenshots show, with zero network cost.
 * Fetching it from Google would be slower, less private, and no more faithful.
 */

/* --------------------------------------------------------------------------
   Tokens
   -------------------------------------------------------------------------- */

:root {
  /* Surfaces */
  --bg: #0f0f0f;
  --surface-1: #1a1a1a;
  --surface-2: #242424;
  --surface-3: #2e2e2e;

  /* Accent. Chalk, deliberately not a hue - see "Theme direction" in the spec.
     The app ships four presets and only these three tokens ever vary; the
     canvas above stays fixed. This page uses Chalk, the default. */
  --accent: #ededed;
  --accent-dim: #c7c7cc;
  --on-accent: #0f0f0f;

  /* Semantic. Exempt from the monochrome rule because they carry function. */
  --success: #4caf50;
  --danger: #f44336;
  --warning: #ff9800;

  /* Text tiers */
  --text-1: #f5f5f5;
  --text-2: #9e9e9e;
  --text-3: #616161;

  /* Borders */
  --border: #2e2e2e;
  --border-focus: #ededed;

  /* Spacing - the app's 4-pt grid, same names. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;
  --space-12: 48px;

  /* Radius. "sm = md = lg = xl = 0" is not a simplification of the spec, it is
     a quotation from it. Pills are the only exception. */
  --radius-panel: 0;
  --radius-full: 9999px;

  /* Type ladder */
  --fs-xs: 12px;
  --fs-sm: 14px;
  --fs-base: 16px;
  --fs-lg: 18px;
  --fs-xl: 20px;
  --fs-2xl: 24px;
  --fs-3xl: 30px;

  /* A WEB-ONLY step above the app's ladder, and flagged as such rather than
     smuggled in. The app's 3xl (30px) is a phone hero; a 1440px browser is a
     different viewport and 30px reads as body copy on it. Everything else on
     this page stays on the ladder above. */
  --fs-display: clamp(2rem, 1.2rem + 4vw, 3.75rem);

  --font: Roboto, "Helvetica Neue", system-ui, -apple-system, sans-serif;
  /* Numbers, timers and versions. Tabular figures are tenet 3. */
  --font-mono: "Roboto Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Motion */
  --fast: 120ms;
  --normal: 200ms;
  --ease: ease;

  /* Layout */
  --measure: 1120px;
  /* 64, not the app's headerHeight of 56. That token sizes the APP's own
     header, where the content is a title and two icons. This bar holds a 48px
     touch target, and 56 leaves it 4px of air top and bottom - it reads as a
     button jammed into a slot. The 48px minimum is the rule that matters
     (it is a hard safeguard in the spec); the chrome around it is not. */
  --header-h: 64px;
}

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

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

* { margin: 0; }

html {
  color-scheme: dark;
  -webkit-text-size-adjust: 100%;
}

/* Smooth scrolling is MOTION, and the spec is absolute about motion: "All
   motion respects prefers-reduced-motion - it drops to instant." The nav links
   are in-page anchors, so an unguarded `scroll-behavior: smooth` animates the
   whole viewport for someone who asked their OS not to. Guarded like the
   live-dot and the fade-rise. */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

body {
  background: var(--bg);
  color: var(--text-1);
  font-family: var(--font);
  font-size: var(--fs-base);
  /* The spec's body default: base/400, line-height 1.5. */
  line-height: 1.5;
  font-weight: 400;
  min-height: 100vh;
  overflow-x: hidden;
}

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

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

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

/* "Focused inputs: 1px -> use a 2px accent border (borderFocus)". Applied to
   every focusable thing, not just inputs: this page is keyboard-navigable and
   the ring is the only affordance a keyboard user gets. */
:focus-visible {
  outline: 2px solid var(--border-focus);
  outline-offset: 2px;
}

/* Every number on the page. Tenet 3. */
.mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* --------------------------------------------------------------------------
   Shared structure
   -------------------------------------------------------------------------- */

.wrap {
  width: 100%;
  max-width: var(--measure);
  margin-inline: auto;
  /* "section headers and hero content keep space4 horizontal insets" - and the
     mobile-safeguards rule wants at least 16px of edge buffer so a tap never
     collides with an edge-swipe gesture on a curved screen. */
  padding-inline: var(--space-4);
}

/* The eyebrow: "uppercase xs/600 in text-3 with letter-spacing ~0.08em".
   Reproduced exactly. */
.eyebrow {
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-3);
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* The hairline. ONE divider style, 1px in --border, exactly as dividerTheme
   centralises it in the app. This is what replaces cards. */
.rule {
  border: 0;
  border-top: 1px solid var(--border);
}

section { padding-block: var(--space-12); }

/* Every section opens with one of these: eyebrow, heading, lede. It caps the
   measure so a lede never runs to 1120px, and it owns the gap to whatever
   follows - previously each section improvised that with its own margin, which
   is why the spacing above the feature grid and above the swatches disagreed. */
.lead {
  max-width: 62ch;
  margin-bottom: var(--space-8);
}

.lead h2 { margin-block: var(--space-3) var(--space-3); }

h1, h2, h3 { font-weight: 700; line-height: 1.15; letter-spacing: -0.01em; }

/* 2xl on a phone, 3xl on a desktop - both are steps on the app's own ladder.
   A fixed 24px is right in a 390px app shell and reads as body copy under a
   60px hero on a 1280px page. */
h2 { font-size: clamp(var(--fs-2xl), 1.1rem + 1.2vw, var(--fs-3xl)); }
h3 { font-size: var(--fs-lg); }    /* titleMedium  -> lg  */

.lede {
  color: var(--text-2);
  font-size: var(--fs-lg);
  max-width: 60ch;
}

/* --------------------------------------------------------------------------
   Icons

   Material *_outlined only, inlined as SVG. The spec's iconography section is
   unusually strict and worth honouring literally: one style, no emoji ever,
   colour by tier. Stroke-based so they inherit currentColor and stay line
   instruments at every size.
   -------------------------------------------------------------------------- */

.i {
  width: 20px;
  height: 20px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* --------------------------------------------------------------------------
   Wordmark

   "brand wordmark 13/700 (PR in text-1, sonal in accent)". At 13px that is a
   chrome-sized mark; the hero repeats it at display size, which is the one
   place it is allowed to be big.
   -------------------------------------------------------------------------- */

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.01em;
  white-space: nowrap;
}

/* The arc mark. Stroked rather than filled, and it takes `currentColor`, so
   the same markup serves the chalk mast and the text-3 footer. `fill: none`
   is not optional - both arcs are open paths and a fill would close them into
   two solid lozenges. */
.mark {
  width: 20px;
  height: 20px;
  flex: none;
  fill: none;
  stroke: currentColor;
}

.wordmark b { color: var(--text-1); font-weight: 700; }
.wordmark i { color: var(--accent); font-style: normal; font-weight: 700; }

/* --------------------------------------------------------------------------
   Masthead — "Raised" tier: surface1 + a hairline, no shadow.
   -------------------------------------------------------------------------- */

.mast {
  position: sticky;
  top: 0;
  z-index: 20;
  height: var(--header-h);          /* headerHeight = 56 */
  display: flex;
  align-items: center;
  gap: var(--space-4);
  background: var(--surface-1);
  border-bottom: 1px solid var(--border);
}

.mast .wrap {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  width: 100%;
}

.mast nav {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-5);
}

/* Neutral actions are text-2 and NEVER chalk - "a neutral action is never
   chalk and never a filled grey box". Nav links are navigation, so: grey. */
.mast nav a {
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--text-2);
  transition: color var(--fast) var(--ease);
}

.mast nav a:hover { color: var(--text-1); }

.mast .btn { margin-left: auto; }
.mast nav + .btn { margin-left: 0; }

@media (max-width: 720px) {
  .mast nav { display: none; }      /* the CTA survives; the links are below */
}

/* --------------------------------------------------------------------------
   Buttons — colour by INTENT, not by widget. Three roles, per the spec.
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  /* "Minimum interactive height 48dp everywhere" - touchTargetMin. */
  min-height: 48px;
  padding: 0 var(--space-5);
  border-radius: var(--radius-panel);   /* square: a button is a panel */
  font-size: var(--fs-sm);
  font-weight: 700;
  cursor: pointer;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease),
              border-color var(--fast) var(--ease);
}

/* AFFIRMATIVE - the chalk fill. On this page there is exactly one action that
   earns it, and it is Download. Keeping it singular is what makes it read as
   the primary action at a glance. */
.btn-accent {
  background: var(--accent);
  color: var(--on-accent);
}

.btn-accent:hover { background: var(--accent-dim); }
.btn-accent:active { background: var(--accent-dim); }

/* NEUTRAL - ghost. Grey label, hairline border, no fill. */
.btn-ghost {
  color: var(--text-2);
  border: 1px solid var(--border);
}

.btn-ghost:hover { color: var(--text-1); border-color: var(--text-3); }

/* "Disabled controls: opacity 0.4." Used by the download button when there is
   no release to point at. */
.btn[disabled],
.btn[aria-disabled="true"] {
  opacity: 0.4;
  cursor: not-allowed;
  pointer-events: none;
}

.btn .i { width: 18px; height: 18px; }

/* --------------------------------------------------------------------------
   Badge — a genuine pill, so --radius-full. One of the few round things here.
   -------------------------------------------------------------------------- */

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 2px var(--space-2);
  border-radius: var(--radius-full);
  background: var(--surface-3);            /* "surface3 = default badges" */
  color: var(--text-2);
  font-size: var(--fs-xs);
  font-weight: 500;
  white-space: nowrap;
}

/* "Success / danger / warning badge bg: respective colour @ 0.20" */
.badge-warning { background: rgb(255 152 0 / 0.2); color: var(--warning); }
.badge-success { background: rgb(76 175 80 / 0.2); color: var(--success); }
/* "Accent badge bg: accent @ 0.15" */
.badge-accent { background: rgb(237 237 237 / 0.15); color: var(--accent); }

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

.hero { padding-block: var(--space-12) var(--space-12); }

/* CENTRED, and logo-led. The mark is the first thing on the page and the
   headline hangs off it, which is what makes the hero read as a product
   rather than as the top of a document. */
.hero-center {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0;
}

/* The eyebrow is a flex row, so centring the column is not enough. */
.hero-center .eyebrow { justify-content: center; }
.hero-center .lede { max-width: 56ch; }
.hero-center .hero-actions { justify-content: center; }
.hero-center .release-line { justify-content: center; }

/* ------------------------------------------------------------------------
   The hero mark, and its animation

   The mark is two concentric open arcs, which is unusually good raw material:
   they can be DRAWN rather than faded in. `pathLength="100"` on both paths
   (set in render.js) normalises each one to 100 units, so the dash maths below
   needs no measuring and no JS.

   Three things happen, in order:
     1. the whole mark swivels in from -110 degrees and settles (one shot)
     2. the outer arc draws itself, then the inner one follows
     3. from then on the inner arc gives a small periodic swivel

   WHY THE PERPETUAL PART IS A SWIVEL AND NOT A SPIN. A continuously rotating
   open ring is a loading spinner - that is exactly what a spinner looks like -
   and a logo that reads as "please wait" is worse than a static one. So the
   idle animation rests for most of its cycle and then turns 22 degrees and
   comes back. It reads as a tick, not as progress.

   This is also the hero's ONE live moment, which is the budget the app's spec
   sets: "one live moment per context, calm everywhere else." The phone mock's
   breathing live-dot is now in its own section further down, so the two never
   animate in the same view.
   ------------------------------------------------------------------------ */

.hero-mark {
  display: block;
  margin-bottom: var(--space-6);
  color: var(--accent);
}

.hero-mark .mark {
  width: clamp(72px, 8vw, 104px);
  height: clamp(72px, 8vw, 104px);
}

@media (prefers-reduced-motion: no-preference) {
  .hero-mark .mark {
    animation: mark-swivel-in 900ms cubic-bezier(0.22, 0.9, 0.24, 1) both;
  }

  /* Draw the outer arc, then the inner one. `both` holds the from-state
     before the delay elapses, so neither arc flashes at full length first. */
  .hero-mark .mark-outer {
    stroke-dasharray: 100;
    animation: mark-draw 760ms cubic-bezier(0.3, 0.8, 0.2, 1) 80ms both;
  }

  /* Two animations on one element: the draw, then the idle swivel that starts
     after it. Comma-separated rather than on a wrapper, because they both act
     on this path and the second must not restart the first. */
  .hero-mark .mark-inner {
    stroke-dasharray: 100;
    animation:
      mark-draw 620ms cubic-bezier(0.3, 0.8, 0.2, 1) 420ms both,
      mark-idle 7s ease-in-out 1400ms infinite;
    /* Rotate about the ARC's own centre, not the path's bounding box - the
       inner arc is off-centre in its own box, so `fill-box` would make it
       wobble instead of turn. `view-box` resolves 50% 50% to (16,16), which
       is the centre both arcs are struck from. */
    transform-box: view-box;
    transform-origin: 50% 50%;
  }
}

@keyframes mark-draw {
  from { stroke-dashoffset: 100; }
  to   { stroke-dashoffset: 0; }
}

@keyframes mark-swivel-in {
  from { transform: rotate(-110deg) scale(0.86); opacity: 0; }
  to   { transform: none; opacity: 1; }
}

/* Rests for ~72% of the cycle, then turns and comes back. */
@keyframes mark-idle {
  0%, 68%  { transform: rotate(0deg); }
  80%      { transform: rotate(22deg); }
  92%      { transform: rotate(-4deg); }
  100%     { transform: rotate(0deg); }
}

/* The masthead mark gives the same swivel on hover, so the gesture is
   discoverable somewhere it can actually be triggered. */
@media (prefers-reduced-motion: no-preference) {
  .wordmark .mark-inner {
    transform-box: view-box;
    transform-origin: 50% 50%;
    transition: transform var(--normal) var(--ease);
  }

  .wordmark:hover .mark-inner { transform: rotate(180deg); }
}

/* ------------------------------------------------------------------------
   The session section: the mock, with a legend that keys it
   ------------------------------------------------------------------------ */

.session {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-12);
  /* Centred, not `start`. The legend is about half the mock's height, so
     top-aligning it left a block of dead canvas under the third item and
     made the pair look unfinished. */
  align-items: center;
}

@media (max-width: 900px) {
  .session {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-10);
    justify-items: start;
  }
}

.legend { display: flex; flex-direction: column; gap: var(--space-6); }

.legend dt {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--fs-base);
  font-weight: 600;
  color: var(--text-1);
  margin-bottom: var(--space-2);
}

.legend dd { margin: 0; color: var(--text-2); font-size: var(--fs-base); }

/* The legend's glyphs take the SAME semantic colours they have in the mock,
   which is what makes it a key rather than a list. Per the spec's colour
   economy these three are the only coloured things on the page. */
.tone-accent  { color: var(--accent); }
.tone-warning { color: var(--warning); }
.tone-success { color: var(--success); }

.hero h1 {
  font-size: var(--fs-display);
  margin-block: var(--space-4) var(--space-4);
  letter-spacing: -0.03em;
  line-height: 1.02;
}

/* The wordmark at hero scale. Same two-tone rule as the 13px chrome version -
   it is the same mark, so it must not drift. */
.hero h1 b { color: var(--text-1); }
.hero h1 i { color: var(--accent); font-style: normal; }

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-8);
}

/* Tier 2, static: the release line is a READOUT. So it drops to text-3, gets
   no box, no border and no affordance - "if it doesn't react, it must not look
   like it would". */
.release-line {
  margin-top: var(--space-4);
  font-size: var(--fs-xs);
  color: var(--text-3);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  align-items: center;
  min-height: 18px;   /* reserved, so resolving the version does not shift the page */
}

.release-line .sep { color: var(--surface-3); }

/* --------------------------------------------------------------------------
   The phone mock

   CSS-drawn from the ASCII layout in specs/screens/session_active.md. It is
   NOT a screenshot and does not pretend to be one: it is the design system
   rendering itself, built from the same tokens the app uses. That is why it is
   honest to show - every value in here is a token, not a traced pixel.
   -------------------------------------------------------------------------- */

.phone {
  width: 300px;
  flex: none;
  background: var(--bg);
  /* A device bezel is physical geometry, not a panel radius - the same
     exemption the spec grants the logo's curves. */
  border: 1px solid var(--border);
  border-radius: 28px;
  padding: 10px;
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.5);   /* the one allowed overlay shadow */
}

@media (max-width: 900px) { .phone { width: 280px; } }

.screen {
  background: var(--bg);
  border-radius: 20px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  /* 8px of air above the progress bar. WITHOUT IT the 4px bar sits in the
     corner radius, which clips both of its ends - on the rendered page it
     read as a detached grey stub floating above the header rather than as a
     progress bar. At 8px down, a 20px corner has only curved in by ~6px and
     the bar reads as full width. */
  padding-top: 8px;
  /* Tall enough that the content OVERFLOWS slightly and is clipped at the
     bottom of the scroll area. That is deliberate: a mock sized exactly to
     its content has dead canvas above the footer and reads as an empty app,
     while one that runs off the bottom reads as a workout with more below -
     which is what the real screen looks like mid-session. */
  height: 540px;
}

/* The thin session progress bar. Square, per the spec's radius list. */
.p-bar {
  height: 4px;
  background: var(--surface-3);
  flex: none;
}

.p-bar span {
  display: block;
  height: 100%;
  width: 45%;
  background: var(--accent);
  /* "Progress-bar fill 400ms ease" */
  transition: width 400ms var(--ease);
}

.p-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  flex: none;
}

.p-head .i { width: 18px; height: 18px; color: var(--text-3); }
.p-title { font-size: var(--fs-sm); font-weight: 600; flex: 1; min-width: 0; }
.p-title small {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  font-size: 11px;
  font-weight: 400;
  color: var(--text-2);
}

/* The live dot, and the heartbeat. "the live dot breathes (opacity/scale,
   1.6s loop)" - the app's signature motion, so it is the one thing on this
   page that animates forever. */
.live-dot {
  width: 6px;
  height: 6px;
  border-radius: var(--radius-full);
  background: var(--accent);
  flex: none;
}

@media (prefers-reduced-motion: no-preference) {
  .live-dot { animation: breathe 1.6s ease-in-out infinite; }
}

@keyframes breathe {
  0%, 100% { opacity: 1;   transform: scale(1); }
  50%      { opacity: 0.45; transform: scale(0.82); }
}

.p-body { flex: 1; overflow: hidden; padding-bottom: var(--space-2); }

/* The bare `[ + ]` add-exercise affordance, accent because adding is an
   affirmative action. Its own hairline above, like an exercise block. */
.p-add {
  display: flex;
  align-items: center;
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--border);
  color: var(--accent);
}

.p-add .i { width: 20px; height: 20px; stroke-width: 2; }

/* An exercise block's name row: uppercase, tight, with its own hairline above.
   Flat - no fill, no border, no radius. */
.ex-name {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-3) var(--space-4) var(--space-2);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-2);
  border-top: 1px solid var(--border);
}

.ex-name .i { width: 16px; height: 16px; color: var(--text-3); }

/* A set row. Tier 2 by default: flat, no chrome, secondary contrast. */
.set {
  display: grid;
  grid-template-columns: 16px 1fr auto;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  min-height: 34px;
  font-size: var(--fs-sm);
  color: var(--text-2);
}

.set-n { color: var(--text-3); font-size: var(--fs-xs); }
.set-v { color: var(--text-2); }
.set-icons { display: flex; align-items: center; gap: var(--space-2); }
.set-icons .i { width: 15px; height: 15px; }

/* A completed set: the check is success, per "semantic icons follow Colour
   economy - success (completed check)". */
.set .ok { color: var(--success); }
/* The PR marker is Icons.star_outline in WARNING - explicitly NOT a trophy
   emoji, and explicitly not the accent. The spec calls this out twice. */
.set .pr { color: var(--warning); }

/* TIER 3 - the live "you are here" row. accent@0.06 tint plus a 2px accent
   left rail, light content. This is the retired-alternative-free version: a
   solid chalk block was trialled in the app and dropped as too glaring. */
.set-live {
  background: rgb(237 237 237 / 0.06);
  box-shadow: inset 2px 0 0 var(--accent);
  color: var(--text-1);
}

/* Controls inside the live row carry a 2px accent@0.30 contour - "a firm
   dim-grey edge for high-glare gyms, never a bright white outline". */
.field {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 34px;
  padding: 1px var(--space-2);
  border: 2px solid rgb(237 237 237 / 0.3);
  color: var(--text-1);
  font-size: var(--fs-xs);
}

.box {
  width: 16px;
  height: 16px;
  border: 2px solid rgb(237 237 237 / 0.3);
  flex: none;
}

/* The bottom action panel. The affirmative action mid-workout, so: chalk. */
.p-foot {
  flex: none;
  padding: var(--space-3) var(--space-4) var(--space-4);
  border-top: 1px solid var(--border);
}

.p-done {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  height: 44px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: var(--fs-sm);
  font-weight: 700;
}

.p-done .i { stroke-width: 2.4; }

/* The bottom nav. "Raised" tier: surface1 + a top hairline, no shadow.
   navHeight = 56. Inactive icons are text-3, NOT a faded white - the spec's
   closing note calls this out specifically. */
.p-nav {
  flex: none;
  height: 56px;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  background: var(--surface-1);
  border-top: 1px solid var(--border);
}

.p-nav b {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  color: var(--text-3);
  font-size: 10px;      /* "nav tab label 10/500" */
  font-weight: 500;
}

.p-nav b .i { width: 22px; height: 22px; }   /* "primary/nav icons 22-24" */
.p-nav b.on { color: var(--accent); }

/* --------------------------------------------------------------------------
   Feature list — flat rows with hairlines. NOT cards.

   This is the clearest place the spec's "Surface treatment" decision shows up
   on the web: the obvious way to lay out eight features is a grid of bordered
   boxes, and that is exactly the boxed-card look the app retired.
   -------------------------------------------------------------------------- */

.features {
  display: grid;
  /* EXACTLY TWO COLUMNS above the breakpoint, and deliberately not
     `auto-fit`/minmax. This grid draws its own structure out of the cells'
     own borders, which means the column count has to be KNOWN: the vertical
     hairline below is `nth-child(odd)`, and auto-fit silently resolving to
     three columns on a wide viewport puts that rule on the wrong cells and
     leaves a bordered empty cell at the end of the last row. Both content
     sets that use this grid are even-numbered (8 features, 4 tenets), so two
     columns always fill completely. */
  grid-template-columns: minmax(0, 1fr);
  gap: 0;
  margin-top: var(--space-8);
  /* The hairlines come from the row borders below, so the grid draws its own
     structure with no filled cells and no gutters to give it away. */
  border-top: 1px solid var(--border);
}

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

.feature {
  padding: var(--space-6) var(--space-4);
  border-bottom: 1px solid var(--border);
  display: flex;
  gap: var(--space-4);
  align-items: flex-start;
}

/* A vertical hairline between columns, but only when there ARE two columns.
   Without the query the last row grows a stray edge on mobile. */
@media (min-width: 720px) {
  .feature:nth-child(odd) { border-right: 1px solid var(--border); }
}

.feature > .i {
  width: 22px;
  height: 22px;
  color: var(--accent);   /* metric-tile precedent: "optional leading icon in accent" */
  margin-top: 2px;
}

.feature h3 { font-size: var(--fs-base); font-weight: 600; margin-bottom: var(--space-2); }
/* base, not sm. 14px is the app's list-row size, correct at arm's length on a
   phone and too small for prose on a 1280px page. */
.feature p { color: var(--text-2); font-size: var(--fs-base); }

/* --------------------------------------------------------------------------
   Three-up blocks — flat, separated by WHITESPACE, never boxed, no dividers.
   Straight from the spec's "Metric tile" bullet, which is explicit that these
   are separated by space rather than chrome.

   The reason this exists alongside `.features` rather than reusing it: four
   consecutive hairline grids down one page read as a single very long table
   no matter what the copy says. The bordered grid carries the feature list;
   these carry the shorter three-point sections, and the difference in shape is
   what tells a reader they have moved on to a new kind of claim.
   -------------------------------------------------------------------------- */

.trio {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-8) var(--space-8);
}

.trio > div > .i {
  width: 22px;
  height: 22px;
  color: var(--accent);
  margin-bottom: var(--space-3);
}

.trio h3 { font-size: var(--fs-base); font-weight: 600; margin-bottom: var(--space-2); }
.trio p { color: var(--text-2); font-size: var(--fs-base); }

/* --------------------------------------------------------------------------
   Accent presets — the four the app actually ships, as real swatches.
   -------------------------------------------------------------------------- */

.presets {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-6);
}

.preset {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  font-size: var(--fs-xs);
  color: var(--text-2);
}

/* THE SWATCH ONLY. This was `.preset span`, which also matched the hex-code
   span sitting next to it - so `#EDEDED` was given a 14px circle's width and
   overflowed its own bordered box, while the actual swatch collapsed to
   nothing. On the live page the colours were invisible and the hex codes
   spilled over their borders. A class, so it can only ever hit the swatch. */
.preset .sw {
  width: 14px;
  height: 14px;
  border-radius: var(--radius-full);   /* a swatch is a genuine circle */
  flex: none;
}

/* THE FOUR ACCENT PRESETS THE APP SHIPS, as classes rather than inline
   backgrounds. This is not a style preference - the CSP is `default-src
   'self'` with no 'unsafe-inline', and that blocks style ATTRIBUTES outright.
   The first version set `style="background:#EDEDED"` here and the swatches
   rendered fully transparent on the live page; the browser console was full of
   "Applying inline style violates ... The action has been blocked."
   Weakening the CSP to fix four swatches would have been the wrong trade.

   These values are the app's `ThemeAccent` presets and must match the hex
   LABELS in app/content.json. CI asserts that they do, so the duplication is a
   checked invariant rather than a place for them to quietly diverge. */
.sw-chalk { background: #ededed; }
.sw-sky   { background: #afcbff; }
.sw-sage  { background: #bfe8c9; }
.sw-amber { background: #ffd98a; }

/* --------------------------------------------------------------------------
   Download panel

   An "Elevated card" is allowed for exactly three things in the app: modals,
   banners, and skeletons. This is the page's banner - the one moment that
   needs to detach from the canvas - so it takes the accent-tinted banner
   treatment: accent@0.06 fill, accent@0.20 hairline. Same as the app's PR
   banner, and for the same reason: this is the important thing here.
   -------------------------------------------------------------------------- */

.download {
  /* No margin-top: it is the only child of its section, and the section's own
     padding-block already supplies the gap. Both together left ~128px of dead
     canvas above the page's most important element. */
  padding: var(--space-8) var(--space-6);
  background: rgb(237 237 237 / 0.06);
  border: 1px solid rgb(237 237 237 / 0.2);
  /* A GRID, not a flex row with `align-items: center`. As a centred flex row
     the copy column grew to its content and pushed the button block hard to
     the right edge, leaving a gulf across the middle of the panel and the
     button floating at the vertical centre of a tall left column - level with
     nothing in particular. Two sized columns, both aligned to the top, put
     the button beside the heading it belongs to. */
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  gap: var(--space-8);
}

@media (max-width: 760px) {
  .download { grid-template-columns: minmax(0, 1fr); }
}

.download-copy { min-width: 0; }

/* The launcher tile beside the download heading. */
.tile-row {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-bottom: var(--space-3);
}

.app-tile {
  width: 64px;
  height: 64px;
  flex: none;
  display: grid;
  place-items: center;
  /* The adaptive icon's OWN background layer, from the app's colors.xml -
     #16161A, which is a hair lighter than the page's #0F0F0F so the tile
     reads as a tile without needing a border. */
  background: #16161a;
  color: var(--accent);
}

.app-tile .mark { width: 38px; height: 38px; }

/* The heading inside the tile row owns no top margin - the row centres it. */
.tile-row h2 { margin-block: var(--space-1) 0; }

/* The panel's lede sits at base rather than the section-lede 18px: inside a
   bordered banner next to a metadata table, 18px crowds the panel. */
.lede-sm { font-size: var(--fs-base); }

/* The panel's action column. Wraps under the copy below the panel's own
   breakpoint, where the grid has already collapsed to one column. */
.download-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}
.download h2 { margin-bottom: var(--space-2); }

/* The metadata table. Tier 2 static readout: no box, secondary contrast, every
   value mono/tabular so the column does not move between builds. */
.meta {
  margin-top: var(--space-5);
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-2) var(--space-5);
  font-size: var(--fs-xs);
  align-items: baseline;
}

.meta dt {
  color: var(--text-3);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.meta dd { color: var(--text-2); margin: 0; word-break: break-all; }

/* The sideload note. warning, because it is the one thing that will surprise
   someone mid-install - which is what the semantic tier is for. */
.note {
  display: flex;
  gap: var(--space-2);
  align-items: flex-start;
  margin-top: var(--space-5);
  font-size: var(--fs-xs);
  color: var(--text-2);
}

.note .i { width: 16px; height: 16px; color: var(--warning); margin-top: 2px; }

/* The degraded state, shown when there is no release to serve yet. It is a
   readout, so it is styled as one - dashed hairline, the spec's "boundary, not
   button" treatment for a Tier 2 container that cannot avoid having an edge. */
.unavailable {
  margin-top: var(--space-5);
  padding: var(--space-4);
  border: 1px dashed var(--border);
  font-size: var(--fs-sm);
  color: var(--text-2);
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
}

.unavailable .i { color: var(--text-3); margin-top: 2px; }

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

footer {
  border-top: 1px solid var(--border);
  padding-block: var(--space-8);
  color: var(--text-3);
  font-size: var(--fs-xs);
}

footer .wrap {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  align-items: center;
  justify-content: space-between;
}

footer a { color: var(--text-2); }
footer a:hover { color: var(--text-1); }

/* --------------------------------------------------------------------------
   On load: "lists/sections fade+rise (+8px, normal)".

   Gated on prefers-reduced-motion NO-PREFERENCE, so the default for anyone who
   asked their OS to calm down is the finished state, not the animated one. The
   spec requires this: "All motion respects prefers-reduced-motion - it drops to
   instant." Note the elements are visible by default and the animation only
   REPLAYS them; a JS- or animation-dependent reveal would leave the page blank
   if either failed.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: no-preference) {
  .rise {
    animation: rise var(--normal) var(--ease) both;
    animation-delay: var(--d, 0ms);
  }
}

/* The stagger, as classes. Same reason as the swatches: these used to be
   `style="--d:120ms"` and CSP blocked every one of them, so the "staggered"
   fade-rise was in fact all firing at once. Six steps is enough for the
   longest run on the page (the eight-item feature grid caps at d5). */
.d0 { --d: 0ms; }
.d1 { --d: 40ms; }
.d2 { --d: 80ms; }
.d3 { --d: 120ms; }
.d4 { --d: 160ms; }
.d5 { --d: 200ms; }

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

/* Skip link - visible only when focused. */
.skip {
  position: absolute;
  left: -9999px;
  top: var(--space-2);
  z-index: 50;
  background: var(--surface-2);
  color: var(--text-1);
  padding: var(--space-3) var(--space-4);
  font-size: var(--fs-sm);
}

.skip:focus { left: var(--space-4); }

/* --------------------------------------------------------------------------
   Impressum and Datenschutzerklärung

   Long-form German legal prose, which is a different typographic problem from
   the rest of the site: it is read, not scanned. So it gets a narrow measure,
   generous leading and no chrome at all - Tier 2 throughout, because none of
   it reacts to anything.
   -------------------------------------------------------------------------- */

.legal-wrap { padding-block: var(--space-12); }

.legal {
  /* ~70 characters. Wider than that and the eye loses the line return on a
     page this dense; the landing page's 62ch lead is for two-sentence blocks,
     not for a page of it. */
  max-width: 70ch;
}

.legal h1 {
  font-size: var(--fs-3xl);
  margin-bottom: var(--space-2);
}

.legal-meta {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xs);
  color: var(--text-3);
}

/* The English document says outright that it is a translation and that the
   German text governs. That sentence is load-bearing, so it is not styled as
   fine print. */
.legal-note {
  margin-top: var(--space-4);
  padding-left: var(--space-4);
  border-left: 2px solid var(--border);
  color: var(--text-2);
  font-size: var(--fs-sm);
}

.legal-section { padding-block: var(--space-6) 0; }

/* Section headings sit below the page title, so they take the step down the
   ladder rather than the h2 clamp the landing page uses. */
.legal-section h2 {
  font-size: var(--fs-lg);
  font-weight: 600;
  margin-bottom: var(--space-3);
}

.legal p {
  color: var(--text-2);
  margin-bottom: var(--space-3);
}

.legal a { color: var(--text-1); text-decoration: underline; text-underline-offset: 2px; }
.legal a:hover { color: var(--accent); }

/* The postal address § 5 DDG requires. Kept as a block rather than a run-in
   sentence so it can be read, and copied, as an address. */
.addr {
  color: var(--text-1);
  line-height: 1.6;
  margin-bottom: var(--space-3);
}

/* Key/value rows - purpose, legal basis, retention, the authority's address.
   A definition list because that is what it is. */
.rows {
  display: grid;
  grid-template-columns: minmax(9rem, auto) minmax(0, 1fr);
  gap: var(--space-2) var(--space-5);
  margin-bottom: var(--space-3);
  font-size: var(--fs-sm);
}

.rows dt {
  color: var(--text-3);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-size: var(--fs-xs);
  padding-top: 2px;
}

.rows dd { margin: 0; color: var(--text-2); }

@media (max-width: 560px) {
  /* Two columns at 9rem + text is a shredded right column on a phone. */
  .rows { grid-template-columns: minmax(0, 1fr); gap: var(--space-1); }
  .rows dd { margin-bottom: var(--space-3); }
}

.legal ul {
  margin: 0 0 var(--space-3);
  padding-left: var(--space-5);
  color: var(--text-2);
}

.legal li { margin-bottom: var(--space-1); }

.legal-wrap .rule { margin-block: var(--space-12); }

/* The footer's legal links. They sit between the wordmark and the credit line,
   and they are the reason the footer became a shared function. */
.foot-nav {
  display: flex;
  gap: var(--space-5);
  flex-wrap: wrap;
}

.foot-nav a { color: var(--text-2); }
.foot-nav a:hover { color: var(--text-1); }

/* --------------------------------------------------------------------------
   The install steps

   A real <ol>, so a screen reader announces "3 of 6" - in a procedure that is
   the most useful thing it can say. The marker is replaced with a counter so
   the number can be `mono` and tabular like every other number on the page.
   -------------------------------------------------------------------------- */

.steps {
  list-style: none;
  counter-reset: step;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--border);
}

.steps li {
  counter-increment: step;
  display: grid;
  /* The number, then ONE content column. The step's heading and body must be
     wrapped in a single element for this to hold - as two children the body
     falls into the next row's number cell and renders one word per line. */
  grid-template-columns: 2.5rem minmax(0, 1fr);
  gap: var(--space-4);
  padding: var(--space-5) 0;
  border-bottom: 1px solid var(--border);
}

.steps li > div { max-width: 70ch; }

/* The step number, drawn from the counter rather than typed into the content -
   so inserting a step cannot leave the list numbered 1,2,3,3,4. */
.steps li::before {
  content: counter(step);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xl);
  font-weight: 700;
  color: var(--text-3);
  line-height: 1.2;
}

.steps h3 { font-size: var(--fs-base); font-weight: 600; margin-bottom: var(--space-2); }
.steps p { color: var(--text-2); }

/* Android's own setting names, called out mid-sentence. text-1 rather than
   accent: they are labels to find on a screen, not actions on this page, and
   the spec reserves chalk for the affirmative action. */
.steps strong { color: var(--text-1); font-weight: 600; }

.steps code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  color: var(--text-1);
  background: var(--surface-2);
  padding: 1px var(--space-1);
}

#install .note { margin-top: var(--space-6); max-width: 70ch; }
