/* reckoner.fit foundations: the layer every redesigned screen is built on.
   FM-52 chip C1. Vanilla CSS, zero build step, no webfonts.

   This file is NOT loaded by any live page. The redesign is built alongside the
   current site and shown only to the accounts in RECKONER_INTERNAL_EMAILS, so
   the pages that use it carry the `next-` prefix that serve.py gates. The
   stylesheet itself does not, and that is deliberate: a gated page's assets stay
   public, the same arrangement `wrist-9c41f7b2.html` has with `wrist.css`. It
   holds no content, gating it would break the page for the one reader allowed to
   see it, and links_test.py fetches every asset as an ordinary athlete, so a
   gated stylesheet is a red build. Note the hyphen doing the work: `next.css` is
   public, `next-anything.css` would not be.

   Three things live here and nothing else does. Components and screens come in
   the chips after this one.

     1. THE TYPE SCALE. Eight steps, and the rule is the token or nothing. No
        rule in this file or any file after it may write a bare px font-size.
        See "Type" below for the eight, for why a ninth is not being added, and
        for the table of prototype values that were resolved onto them.
     2. THE COLOUR TOKENS, every one of which clears WCAG AA against every
        surface it can sit on. scripts/foundations_test.py recomputes all of it
        from this file on every run, so a token cannot be nudged back under 4.5
        by anybody who did not mean to.
     3. THE LAYOUT. Real min-width breakpoints, and the per-screen column caps
        the design specifies. Until now every breakpoint on the site was a
        max-width and there was no min-width anywhere, so the column never grew
        and half of a 1440px window was empty on every signed-in screen.

   Read Handovers/HANDOVER-website-platform.md before changing a value
   here. Section 5 is the list of decisions a redesign must not undo. */


/* ==========================================================================
   1. TOKENS
   ==========================================================================

   Light is the `:root` set. Dark arrives two ways and both are needed: the
   media query follows the device for an athlete who has expressed no
   preference, and the attribute is the explicit choice the profile menu writes.
   The two dark blocks are byte identical on purpose and foundations_test.py
   holds them that way, because a value edited in one and not the other is a
   theme that disagrees with itself depending on how it was reached.

   A public or logged-out page pins `data-theme="dark"` in its markup and shows
   no theme control at all. That is a product decision, not a styling one: the
   switch is a signed-in feature. The attribute selectors below are written
   without `:root` so a page can also mark a single element, which is what the
   foundations preview does to draw both themes at once. */

:root,
[data-theme="light"] {
  /* Brand identity. These never flip with the theme: they are the mark, not the
     surface. Oxide carries two values because plain oxide goes muddy on
     graphite. Oxide is NOT a general UI accent. It belongs to the mark and to
     warm highlights, and the action accent below is the calm blue. */
  --chalk: #d9cfbc;
  --iron: #33291f;
  --oxide: #a33f22;
  --oxide-lift: #c95f36;
  --bone: #f2f0e9;
  --graphite: #17150f;

  --surface-0: #f6f5f1;   /* the page */
  --surface-1: #efeee8;   /* an inset tile, and a quiet control's hover fill */
  --surface-2: #ffffff;   /* a raised card */
  --surface-3: #e5e2d9;   /* a well inside a card, and avatar grounds */

  /* Warm lines rather than black at low alpha, so a hairline over a bone page
     reads as part of the paper instead of as a pencil mark on top of it. */
  --border: rgba(51, 41, 31, .12);
  --border-strong: rgba(51, 41, 31, .24);
  /* The top bar's own edge, and the one line on the site that is a background
     layer rather than a border. A flat warm grey pitched between the page below
     it and the white masthead above. */
  --topbar-edge: #e8e5de;

  --text-primary: #23221e;
  --text-secondary: #5c5a53;
  /* Darkened from the delivered #6b6860, which failed AA on --surface-3 at 4.30.
     This clears every surface with headroom, 4.63 on the worst of them, rather
     than sitting on the line the way #67645c does at 4.56. Below about #646159
     the visible gap to --text-secondary closes and the three-step ramp stops
     reading as three steps, so the usable range is narrow and this sits in the
     middle of it. Darkening only raises contrast in light theme, so nothing
     else moved. Dark theme was already clear and is untouched. */
  --text-muted: #66635b;

  /* The UI action accent: links and primary buttons. */
  --accent: #185fa5;
  --accent-bg: #e6f1fb;
  --success: #0f6e56;
  --success-bg: #e1f5ee;
  --warning: #854f0b;
  --warning-bg: #faeeda;
  /* A personal record. Oxide, because the mark's top plate is the reckoned
     increment and a lift that has not moved in a month moving is exactly that.
     It is the one semantic colour the brand palette could supply directly. */
  --record: #a33f22;
  --record-bg: #f8e9e2;

  /* Something failed. Added by chip C4, at the delivered values, because none of
     the four above says it: the accent is an action, success is relief, warning
     asks the athlete for something, and the record is a win. An error screen and
     an inline field error had no colour to draw with, and a screen chip inventing
     one privately is four screens with four reds. It clears AA on all four
     surfaces over its own tint in both themes at the delivered values, so nothing
     had to move. See section 4 for what draws it. */
  --alert: #a01f3c;
  --alert-bg: #f7e4e8;

  /* A filled button: the fill, and the ink drawn on it. Also added by C4,
     because the error screen's retry is the first filled button the redesign
     draws and it needed one.

     It is a pair rather than "--accent with white on it", and that is not
     tidiness. --accent is a foreground token, tuned to be READ against the four
     surfaces, so in dark theme it is a light blue: white on it is 2.11, a
     failure this file would have shipped on the one button an athlete presses
     when something has already gone wrong. So the fill inverts with the theme
     exactly as the delivered design's --btn-bg does, in the README's blue rather
     than the prototype's oxide. Light is 6.52, dark is 8.67. */
  --btn-bg: #185fa5;
  --btn-fg: #ffffff;

  /* The fill of a loading skeleton. A background rather than a foreground, so it
     is the one colour token here with no contrast requirement and no `-bg` pair:
     nothing is ever drawn on top of it. */
  --skel: rgba(51, 41, 31, .09);
}

/* Dark. One value in here moved for contrast and is worth naming, because it is
   the token most likely to be "corrected" back by somebody comparing this file
   with app.css. `--record` lightens from the live #d97a4e. That is not a bug
   being fixed on the live site: live has no --surface-3, and the two places it
   draws the badge today are a tinted banner on the page at 5.22 and an untinted
   receipt on a card at 4.86, both clear. This system asks more of it. A
   semantic colour here has to survive its own tint over any of the four
   surfaces, and #d97a4e is 4.32 of those over a card and 3.97 over a well. */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --surface-0: #17150f;
    --surface-1: #201d16;
    --surface-2: #2a271f;
    --surface-3: #322e25;
    --border: rgba(242, 240, 233, .12);
    --border-strong: rgba(242, 240, 233, .24);
    --topbar-edge: rgba(242, 240, 233, .10);
    --text-primary: #f2f0e9;
    --text-secondary: #c3bfb4;
    --text-muted: #9b978b;
    --accent: #85b7eb;
    --accent-bg: rgba(23, 95, 165, .22);
    --success: #5dcaa5;
    --success-bg: rgba(15, 110, 86, .22);
    --warning: #ef9f27;
    --warning-bg: rgba(133, 79, 11, .24);
    --record: #e69268;
    --record-bg: rgba(163, 63, 34, .18);
    --alert: #f08098;
    --alert-bg: rgba(160, 31, 60, .30);
    --btn-bg: #85b7eb;
    --btn-fg: #17150f;
    --skel: rgba(242, 240, 233, .10);
  }
}

[data-theme="dark"] {
  --surface-0: #17150f;
  --surface-1: #201d16;
  --surface-2: #2a271f;
  --surface-3: #322e25;
  --border: rgba(242, 240, 233, .12);
  --border-strong: rgba(242, 240, 233, .24);
  --topbar-edge: rgba(242, 240, 233, .10);
  --text-primary: #f2f0e9;
  --text-secondary: #c3bfb4;
  --text-muted: #9b978b;
  --accent: #85b7eb;
  --accent-bg: rgba(23, 95, 165, .22);
  --success: #5dcaa5;
  --success-bg: rgba(15, 110, 86, .22);
  --warning: #ef9f27;
  --warning-bg: rgba(133, 79, 11, .24);
  --record: #e69268;
  --record-bg: rgba(163, 63, 34, .18);
  --alert: #f08098;
  --alert-bg: rgba(160, 31, 60, .30);
  --btn-bg: #85b7eb;
  --btn-fg: #17150f;
  --skel: rgba(242, 240, 233, .10);
}


/* --------------------------------------------------------------------------
   Type

   Eight steps and no ninth. The delivered prototypes declare these eight and
   then write 129 hard-coded off-scale values across 18 further sizes, and the
   live site runs 19 distinct sizes of its own including three fractional ones.
   Both of those are the same problem: an author reaching for a number instead
   of a step. So every off-scale value was resolved onto the nearest step, ties
   rounding up, and the record of that is here rather than in a commit message
   nobody reads again:

     10, 11, 11.5, 12.5 -> --t-micro 12      21           -> --t-lede 19
     13, 13.5           -> --t-small 14      22, 25, 26   -> --t-card 24
     15, 17             -> --t-body 16       28, 30, 34, 36 -> --t-page 32
     18, 20             -> --t-lede 19       0.88em       -> --t-small 14

   The one live value that was not a fixed size, `clamp(32px, 4.4vw, 46px)` on
   the landing headline, becomes --t-fig, whose own ramp does the same job in
   steps the rest of the site already uses.

   A ninth step was not added because none of the resolutions above lands more
   than three pixels from a step that already exists, and because a scale earns
   its keep by being short. If a screen genuinely needs a step that is not here,
   it gets added once, to this block, and every screen gets it.

   Two of the eight ramp with the viewport, and the rule they follow is that a
   shrunk step lands exactly on another step. So the site still only ever draws
   these eight numbers, whatever the width:

              under 700px   700 to 1099   1100px and up
     --t-fig      32             44             44
     --t-hero     44             44             64

   That is the whole of the responsive type story. The six steps below --t-fig
   are fixed at every width: 64px is unreadable on a 375px phone in a way 24px
   is not, and a body size that moves is a body size an athlete has to re-learn
   on the treadmill screen after learning it at their desk.

   Weight tops out at 500. "Bold" never goes heavier, and foundations_test.py
   fails a rule that asks for more. */

:root {
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-voice: Georgia, "Times New Roman", serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;

  --t-micro: 12px;
  --t-small: 14px;
  --t-body: 16px;
  --t-lede: 19px;
  --t-card: 24px;
  --t-page: 32px;
  --t-fig: 32px;
  --t-hero: 44px;

  /* Icons are sized in px like text but are not text, so they get an axis of
     their own rather than borrowing steps from the scale and blurring what a
     step means. Three sizes: beside micro and small copy, beside body copy, and
     in a nav or menu row. The prototypes' 11, 13, 15 and 17 resolve here on the
     same nearest-step-ties-up rule as the text above. */
  --i-sm: 14px;
  --i-md: 16px;
  --i-lg: 18px;

  --lh-body: 1.55;
  --lh-head: 1.12;
  --ls-head: -.02em;
  --ls-display: -.03em;   /* --t-fig and --t-hero only */
  --ls-eyebrow: .14em;    /* .16em on public pages, see .rk-eyebrow--public */
}


/* --------------------------------------------------------------------------
   Radius, depth and motion

   Depth is surface tint plus a hairline border. Never a shadow, and never a
   coloured left border on a card. The two shadow tokens below exist for
   floating overlays only, which on this site means the account menu, a modal
   and the bottom banner. foundations_test.py refuses any other non-inset
   box-shadow in this file. */

:root {
  --r: 4px;               /* controls */
  --r-card: 12px;         /* cards */
  --r-pill: 999px;

  --shadow-overlay: 0 18px 40px rgba(23, 21, 15, .24);
  --shadow-banner: 0 -8px 24px rgba(23, 21, 15, .24);

  /* Colour and fill changes on hover and selection. No bounces, no parallax, no
     entrance animations, and theme switching is instant. */
  --motion: .12s ease;

  /* A thumb in a gym, not a mouse at a desk. Every control the athlete presses
     mid-session is at least this tall. */
  --tap: 48px;
}


/* ==========================================================================
   2. LAYOUT
   ==========================================================================

   Two min-width breakpoints, which is two more than the site had. Everything
   below is written narrow first, so the base rules are the phone and the
   queries widen them:

     700px   a tablet held either way, and the width at which a two-column
             layout first has room to be one. Gutters open up and --t-fig
             reaches its full 44px.
     1100px  a laptop. Gutters open again and --t-hero reaches 64px. This is
             also the width above which every column cap below actually binds,
             which is the point: the column stops growing and the page stops
             being a single measure of text dragged across a 27 inch monitor.

   Custom properties cannot be used inside a media query condition, so the two
   numbers are written literally at each use. They are named here so the reader
   knows there are exactly two and what each one is for. */

:root {
  --gutter: 20px;
  --col-top: 34px;
  --col-bottom: 90px;
}

@media (min-width: 700px) {
  :root {
    --gutter: 28px;
    --t-fig: 44px;
  }
}

@media (min-width: 1100px) {
  :root {
    --gutter: 40px;
    --t-hero: 64px;
  }
}

/* The centred column. Every screen is one of these, and the cap is written on
   the element as `data-col` so the value in the markup is the value in the
   design rather than a class name somebody has to look up.

   The caps are the design's, screen by screen:

     1200  the week, admin
     1000  history, spec
      900  where you stand, library index
      820  log, setup, account, watches, fuel, founder
      760  report, and the body of an article or guide
     1120  the public page shell

   `data-sticky` is for a screen with a sticky action bar along the bottom,
   which needs the taller bottom padding or the last row sits under the bar. */

.rk-col {
  --col: 900px;
  width: 100%;
  max-width: var(--col);
  margin-inline: auto;
  padding: var(--col-top) var(--gutter) var(--col-bottom);
}

.rk-col[data-col="760"] { --col: 760px; }
.rk-col[data-col="820"] { --col: 820px; }
.rk-col[data-col="900"] { --col: 900px; }
.rk-col[data-col="1000"] { --col: 1000px; }
.rk-col[data-col="1120"] { --col: 1120px; }
.rk-col[data-col="1200"] { --col: 1200px; }

.rk-col[data-sticky] { padding-bottom: 120px; }


/* ==========================================================================
   3. BASE
   ========================================================================== */

* { box-sizing: border-box; }

/* Pin the text size against iOS Safari's auto-inflation. Left to itself, Safari
   boosts small text inside a full-width block on a narrow screen, so a caption
   renders bigger than the rows above it. 100% means "draw it at the size the
   CSS asked for", which is what every step of the scale wants. */
html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--surface-0);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
}

/* Headings carry the step, the tight leading and the negative tracking as one
   thing, so a screen writes <h1> and gets the design rather than three
   properties it has to remember. The two display steps track tighter again. */
h1, h2, h3, h4 {
  margin: 0;
  font-weight: 500;
  line-height: var(--lh-head);
  letter-spacing: var(--ls-head);
}

h1 { font-size: var(--t-page); }
h2 { font-size: var(--t-card); }
h3 { font-size: var(--t-lede); }
h4 { font-size: var(--t-body); }

.rk-hero,
.rk-fig {
  margin: 0;
  font-weight: 500;
  line-height: var(--lh-head);
  letter-spacing: var(--ls-display);
}

.rk-hero { font-size: var(--t-hero); }
.rk-fig { font-size: var(--t-fig); }

p { margin: 0; }

.rk-lede {
  font-size: var(--t-lede);
  color: var(--text-secondary);
}

.rk-small { font-size: var(--t-small); }
.rk-micro { font-size: var(--t-micro); }

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

/* Numbers, codes and macros. Tabular figures so a column of loads lines up and
   a changing rest timer does not shuffle sideways once a second. */
.rk-num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* An italic aside in the coach's voice. */
.rk-voice {
  font-family: var(--font-voice);
  font-style: italic;
}

/* The label above a heading. Uppercase at the smallest step, widely tracked. */
.rk-eyebrow {
  display: block;
  font-size: var(--t-micro);
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* Public pages track wider and colour the eyebrow with the accent. */
.rk-eyebrow--public {
  letter-spacing: .16em;
  color: var(--accent);
}

a {
  color: var(--accent);
  text-decoration: none;
}

a:hover { text-decoration: underline; }

/* One focus ring for the whole site, drawn outside the control so it never
   moves the layout, and never removed without a replacement. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r);
}

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

/* A card. Surface tint and a hairline, which is the whole depth system. */
.rk-card {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
}

/* A well inside a card: one step further in, not one step further up. */
.rk-well {
  background: var(--surface-3);
  border-radius: var(--r);
}

.rk-hair {
  border: 0;
  border-top: 1px solid var(--border);
  margin: 0;
}

[disabled],
.rk-disabled {
  opacity: .5;
  cursor: not-allowed;
}

/* Anything the athlete presses during a session. */
.rk-tap {
  min-height: var(--tap);
  min-width: 56px;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}


/* ==========================================================================
   4. STATES
   ==========================================================================

   FM-52 chip C4, appended below everything C1 wrote so a rebase against another
   chip touching this file is mechanical. Nothing above this line moved except
   three token declarations, marked where they sit.

   The four screen states, the state banner and the inline field error, drawn
   once here and built by js/states.js. Every redesigned screen gets these rather
   than writing its own, which is the whole point: before this, the live site had
   an empty state in review.js, a different one in profile.js, a spinner nowhere
   at all, and five spellings of "that did not work".

   **This is the design's, not a proposal.** The brief for this chip said the
   bundle lists these as something to build rather than something it drew. That
   is what its README says, and the README is wrong about its own prototypes:
   the bundle's app prototype draws all four on Today (`isLoading` at 1707,
   `isError` at 1727, `isFirst` at 1740, the banner at 1776), declares `--skel`
   and `@keyframes skel` in its token block, and its spec screen carries a
   `stateRows` table naming all eight of Today's states with the rule for each
   plus a `compRows` entry reading "new: .skel, loading skeletons that mirror the
   real block geometry, animated at 1.4s, dropped under prefers-reduced-motion".
   So every value below is followed rather than chosen. The two places this file
   decides something the design did not are marked HELD in the comment above
   them. */


/* --------------------------------------------------------------------------
   The page head

   Eyebrow, then the h1, then an optional lede. Six of the nine signed-in screens
   have no visible page title at all today (the week, log, fuel, setup, account
   and admin), while their <title> tags are correct, so the tab says one thing
   and the document says nothing. That is an accessibility gap as much as a
   design one: a screen reader's heading list is empty on six screens.

   Fixed here rather than on the live pages on purpose. Adding an h1 to
   `review.html` today would be a redesign dribbling onto the current site, which
   is the thing the nothing-goes-offline rule exists to prevent, so the six
   headings are built into the redesign and land with their screens in C3 and C5.
   js/states.js carries the words. */

.rk-head { display: block; }
.rk-head > h1 { margin-top: 8px; }
.rk-head > .rk-lede { margin-top: 10px; max-width: 56ch; text-wrap: pretty; }


/* --------------------------------------------------------------------------
   Loading

   Skeletons at the real block geometry plus one honest line. Never a blank page,
   and never a spinner: a spinner says "something is happening", a skeleton says
   "this is the shape of what is coming", and on a phone in a gym the second is
   the one worth drawing.

   The heights below are the prototype's, and they are heights rather than font
   sizes, so no step of the type scale is involved and none is invented. */

.rk-skel { animation: rk-skel 1.4s ease-in-out infinite; }

.rk-skel-bar,
.rk-skel-block {
  background: var(--skel);
  border-radius: var(--r);
}

.rk-skel-block { border-radius: var(--r-card); }

/* The four shapes a screen asks for by name. Each mirrors what the real screen
   draws in that slot, which is what makes the swap to real content read as the
   page filling in rather than as the page changing. */
.rk-skel-eyebrow { width: 120px; height: 12px; }
.rk-skel-title { width: min(340px, 70%); height: 34px; margin-top: 14px; }

.rk-skel-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px;
  margin-top: 28px;
}

.rk-skel-tiles > .rk-skel-block { height: 96px; }

.rk-skel-panes {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
  margin-top: 24px;
  align-items: start;
}

.rk-skel-panes > .rk-skel-block:first-child { height: 420px; }
.rk-skel-panes > .rk-skel-block:last-child { height: 280px; }

@media (min-width: 700px) {
  .rk-skel-panes { grid-template-columns: 1.35fr 1fr; }
}

.rk-skel-rows { display: grid; gap: 12px; margin-top: 24px; }
.rk-skel-rows > .rk-skel-block { height: 64px; }

.rk-skel-line { margin-top: 20px; }

@keyframes rk-skel {
  0%, 100% { opacity: .55; }
  50% { opacity: 1; }
}

/* The global reduced-motion rule above would leave this frozen at whatever
   opacity the .01ms run happened to land on, which is an accident rather than a
   decision. Stated instead: no pulse, one steady placeholder weight. */
@media (prefers-reduced-motion: reduce) {
  .rk-skel {
    animation: none;
    opacity: .6;
  }
}


/* --------------------------------------------------------------------------
   Empty, and error

   One block, two tones. Both are a narrow centred column with an eyebrow, a
   sentence-length heading, a paragraph and up to two actions, and they differ in
   the eyebrow's colour and in what the copy has to carry. That is the design's
   arrangement on Today and it is worth keeping as one component: an athlete who
   has learned to read the error screen reads the empty one without learning it
   twice.

   The error screen carries a fourth part the empty one does not: the endpoint,
   the status and the time, in mono, for whoever reads the report. */

.rk-state {
  max-width: 560px;
  margin-inline: auto;
  padding: 96px var(--gutter);
}

/* h1 or h2, depending on whether this state replaces the screen or lands under
   its heading. The tag is about the outline and the size is this rule's, so both
   are drawn at the page step and a screen reader still reads one page title. See
   the note above `state` in js/states.js. */
.rk-state > h1,
.rk-state > h2 { margin-top: 10px; font-size: var(--t-page); }
.rk-state > p { margin-top: 14px; max-width: 46ch; text-wrap: pretty; }
.rk-state-eyebrow { color: var(--text-muted); }
.rk-state[data-tone="error"] .rk-state-eyebrow { color: var(--alert); }

.rk-state-actions {
  display: flex;
  gap: 10px;
  margin-top: 24px;
  flex-wrap: wrap;
}

/* The action on a state screen, drawn to the bundle's button spec: primary is
   the accent filled with white on it, secondary is transparent behind a strong
   hairline, hover is brightness on the filled one and a surface tint on the
   quiet one, and there is no shrink-on-press.

   C4 wrote this scoped to the state screens and said the general button belonged
   to whichever screen chip drew a screen full of them, and that when it arrived
   these two would collapse into it with no visual change. C3 is that chip, and
   the collapse is the four selectors below rather than a second block: `.rk-btn`
   IS this, under the name a screen uses. Nothing about the state screens moved.

   The one thing that is not shared is the height. 44px is right for a retry on
   an error page and wrong for a control an athlete presses between sets with
   cold hands, so `.rk-btn--tap` takes it to the 48px thumb target. Two names for
   one look and two heights, rather than two blocks that drift. */
.rk-btn,
.rk-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 18px;
  min-height: 44px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  background: transparent;
  color: var(--text-primary);
  font-size: var(--t-body);
  text-decoration: none;
  cursor: pointer;
  transition: background var(--motion), filter var(--motion);
}

.rk-btn:hover,
.rk-action:hover { background: var(--surface-1); text-decoration: none; }

.rk-btn--primary,
.rk-action--primary {
  background: var(--btn-bg);
  border-color: var(--btn-bg);
  color: var(--btn-fg);
}

.rk-btn--primary:hover,
.rk-action--primary:hover { background: var(--btn-bg); filter: brightness(1.06); }

/* The in-session size. See the note above the block. */
.rk-btn--tap { min-height: var(--tap); padding: 0 22px; }

/* The receipt. Mono because it is copied into a report rather than read, and
   muted because nobody who is not writing that report needs it. */
.rk-state-receipt {
  margin-top: 18px;
  font-family: var(--font-mono);
  font-size: var(--t-small);
  color: var(--text-muted);
}

/* An empty state that sits inside a card or a list rather than replacing the
   whole screen. Same words, no page padding, no huge heading. */
.rk-state[data-inset] {
  max-width: none;
  padding: 34px 0 38px;
}


/* --------------------------------------------------------------------------
   The state banner

   One component for every "the page is fine, and here is the fact about it"
   case: rest day, deload week, paused, honestly small, a sample plan, a watch
   running an old app. A hairline card with a coloured left edge.

   **That left edge is the one place this layer contradicts its own README.** The
   bundle's README says "never a coloured left-border accent on a card" and this
   file repeats it in section 1. The prototype then draws the banner with
   `border-left: 3px solid` in four variants, and the spec screen defines the
   component as "hairline card with a 3px left edge in oxide or teal". The
   reading that makes both true is that the rule is about CARDS, which carry
   content and stay flat, and the banner is a different component whose left edge
   is what tells it apart from a card at a glance. Implemented that way, and the
   contradiction is raised rather than absorbed: **HELD for Design.** If the rule
   wins instead, this is one declaration and the tone colours stay.

   Tones are named for the role, not the colour, so the copy decides and the
   palette can move under it:

     relief  planned easing. Rest day, deload week. Never the gain colour.
     note    a fact about the plan. Paused, honestly small, a sample week.
     alert   something needs doing. An out-of-date watch app.

   `relief` takes --success rather than the prototype's teal --ease, which is
   the mapping chip C2 already made for the reasoning layer's Backed off verdict.
   One role, one token, rather than a second name for the same job. */

.rk-banner {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  padding: 16px 18px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-left: 3px solid var(--tone, var(--accent));
  border-radius: var(--r-card);
}

.rk-banner[data-tone="relief"] { --tone: var(--success); }
.rk-banner[data-tone="note"] { --tone: var(--accent); }
.rk-banner[data-tone="alert"] { --tone: var(--alert); }

.rk-banner > i { color: var(--tone, var(--accent)); font-size: var(--i-lg); flex: none; }
.rk-banner-body { min-width: 0; }
.rk-banner-body > p { max-width: 62ch; }
.rk-banner-body > p + p { margin-top: 5px; }


/* --------------------------------------------------------------------------
   The inline field error

   The one error surface the prototype draws more than once: a line of alert-red
   copy directly under the control that refused, at the small step. Pairing code,
   sign-in code, display name, bodyweight, body fat and the admin suspend reason
   all use it, so it is the commonest error on the site by a distance.

   Announced to assistive tech by js/states.js rather than by CSS, because the
   whole failure here is a sighted athlete seeing red text a screen reader never
   mentions. */

.rk-field-error {
  margin-top: 10px;
  font-size: var(--t-small);
  line-height: 1.5;
  color: var(--alert);
}


/* ==========================================================================
   5. TODAY, AND LOG
   ==========================================================================

   FM-52 chip C3, appended below everything C1 and C4 wrote for the same reason
   C4 gave: another chip is in this file at the same time, so a rebase should be
   a append rather than a merge. Four selectors in section 4 gained a `.rk-btn`
   name and nothing above this line moved otherwise.

   These are the two screens an athlete sees most, and they are drawn from the
   bundle's own `data-screen-label="Today"` and `="Log"` blocks. What is NOT
   here is anything either screen could have borrowed: the head, the skeletons,
   the empty state, the error screen and the state banner all come from
   js/states.js, and a screen writing its own is the thing that module exists to
   stop.

   THE TWO MEASUREMENTS THAT ARE REQUIREMENTS RATHER THAN TASTE, both on Log,
   both because the screen is used with chalk on your hands: a set input is at
   least 48px tall and 56px wide, and the lift row is a `1fr auto auto` grid.
   Neither is a preference somebody can tidy away, so each is written against
   `--tap` and against the grid rather than as a number in a rule that reads
   like a guess.

   What the design draws and this does NOT is named rather than quietly dropped:

     the RPE bar chart on a Today lift row. Five bars coloured by effort band,
       which needs a band palette C1 did not add. A sixth colour ramp is a
       decision made once for the whole site, not smuggled in by one row, so
       Today writes `RPE 8` in words and the bars wait for the chip that adds
       the ramp. Where you stand needs the same ramp, so C5 is where it lands.
     the two streak pills in the session card's header. Nothing on this API
       answers "weeks trained back to back": the only streak the product counts
       lives in reckoner/auth.py and is computed for an email. A pill drawing a
       number the page had to invent is worse than a pill that is not there.
     the account menu behind the profile chip. The chip is drawn, with the
       athlete's own name from js/identity.js, and it is a name plate rather
       than a menu: the menu, the theme control and the founder plate belong to
       the chips that own them. */


/* --------------------------------------------------------------------------
   The shell

   A slim bar carrying the mark, the screens that exist, and who is signed in.
   Two entries because two screens are built, and the nav grows in C5 rather
   than shipping five links that 404. The bar's own edge is `--topbar-edge`,
   which is the one line on the site that is a background layer rather than a
   border. */

.rk-top {
  background: var(--surface-2);
  border-bottom: 1px solid var(--topbar-edge);
}

.rk-top-inner {
  display: flex;
  align-items: center;
  gap: 20px;
  max-width: 1200px;
  margin-inline: auto;
  padding: 12px var(--gutter);
}

.rk-mark {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--text-primary);
  font-size: var(--t-lede);
  letter-spacing: var(--ls-head);
}

.rk-mark:hover { text-decoration: none; }
.rk-mark img { display: block; }
.rk-mark b { font-weight: 500; }
.rk-mark span { color: var(--oxide-lift); }

.rk-nav {
  display: flex;
  gap: 4px;
  margin-inline-start: auto;
}

.rk-nav a {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 0 14px;
  min-height: var(--tap);
  border-radius: var(--r);
  color: var(--text-secondary);
  font-size: var(--t-small);
  transition: background var(--motion), color var(--motion);
}

.rk-nav a:hover { background: var(--surface-1); text-decoration: none; }

/* The tab you are on. An inset rule rather than a shadow, which is the one
   shape of depth this system allows anywhere but a floating overlay. */
.rk-nav a[aria-current="page"] {
  color: var(--text-primary);
  background: var(--surface-1);
  box-shadow: inset 0 -2px 0 var(--accent);
}

.rk-nav i { font-size: var(--i-md); }

/* Who is signed in. `js/identity.js` answers what to call them, and the whole
   reason that module exists is that the answer is never the email address. */
.rk-who {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 6px 14px 6px 6px;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  font-size: var(--t-small);
  color: var(--text-primary);
  white-space: nowrap;
}

.rk-disc {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  flex: none;
  border-radius: 50%;
  background: var(--surface-3);
  color: var(--text-secondary);
  font-size: var(--t-body);
}


/* --------------------------------------------------------------------------
   Today: the head, and the strip of figures under it

   The head is the module's `head()` with the session's own words in it, plus
   the one action the screen exists to lead to. They sit on one line at a width
   that has room and stack under it at one that does not, so the button is never
   a full-width slab beside a two-word heading. */

.rk-day-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
}

.rk-day-head > .rk-head { flex: 1 1 320px; }

/* Hairlines drawn as gaps over a `--border` ground, so four cells share three
   rules and the outer edge is the card's own border rather than a fifth. */
.rk-figs {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1px;
  margin-top: 26px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  overflow: hidden;
}

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

@media (min-width: 1100px) {
  .rk-figs { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

.rk-fig-cell {
  background: var(--surface-2);
  padding: 16px 18px;
}

.rk-fig-cell > .rk-eyebrow i { font-size: var(--i-sm); margin-inline-end: 7px; }

/* The number itself. `--t-page` rather than the prototype's inline 34px, which
   is one of the 129 off-scale values section 1 of this file resolved. */
.rk-fig-value {
  margin-top: 8px;
  font-size: var(--t-page);
  font-weight: 500;
  line-height: 1.08;
  letter-spacing: var(--ls-display);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.rk-fig-value small {
  font-size: var(--t-lede);
  color: var(--text-secondary);
}

.rk-fig-sub {
  display: flex;
  align-items: baseline;
  gap: 9px;
  flex-wrap: wrap;
  margin-top: 3px;
  font-size: var(--t-small);
  color: var(--text-secondary);
}

/* A word that acts. Not a `.rk-btn`: it sits inside a sentence, and a filled
   control there would out-shout the figure above it. */
.rk-inline-btn {
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--accent);
  font-size: var(--t-small);
  cursor: pointer;
}

.rk-inline-btn:hover { text-decoration: underline; }


/* --------------------------------------------------------------------------
   Today: the two panes

   The session on the left and what the engine decided on the right, stacked on
   a phone. The ratio is the design's. */

.rk-panes {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
  margin-top: 24px;
  align-items: start;
}

@media (min-width: 1100px) {
  .rk-panes { grid-template-columns: 1.35fr 1fr; }
}

.rk-session { overflow: hidden; }

.rk-session-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 16px 20px;
}

.rk-session-head p { margin-top: 2px; }

/* A fact about the session, on a hairline pill. */
.rk-pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 11px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-pill);
  font-size: var(--t-micro);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-secondary);
  white-space: nowrap;
}

.rk-pill-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
}

.rk-pill[data-state="off"] .rk-pill-dot { background: var(--text-muted); }

/* The readiness strip. An offer rather than a question: Steady is the no-op and
   is preselected, so a session nobody touches this on is exactly what it was. */
.rk-ready {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 12px 20px;
  background: var(--surface-1);
  border-block: 1px solid var(--border);
}

.rk-ready-say { flex: 1 1 220px; }

.rk-chips { display: flex; gap: 6px; flex-wrap: wrap; }

.rk-chip {
  padding: 8px 14px;
  min-height: 38px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--text-secondary);
  font-size: var(--t-small);
  cursor: pointer;
  transition: background var(--motion), color var(--motion), border-color var(--motion);
}

.rk-chip:hover { background: var(--surface-2); }

.rk-chip[aria-pressed="true"] {
  border-color: var(--accent);
  background: var(--accent-bg);
  color: var(--text-primary);
}

/* One prescribed lift. `1fr auto` on Today, because the load and the effort
   share the right-hand column and everything else runs full width beneath. */
.rk-lift {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 4px 20px;
  padding: 18px 20px;
  border-top: 1px solid var(--border);
}

.rk-lift-name {
  display: flex;
  align-items: center;
  gap: 10px 14px;
  flex-wrap: wrap;
}

.rk-lift-right {
  grid-column: 2;
  grid-row: 1 / span 2;
  text-align: right;
}

.rk-lift-load {
  font-family: var(--font-mono);
  font-size: var(--t-lede);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.rk-lift-rpe {
  margin-top: 7px;
  font-size: var(--t-micro);
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.rk-lift-scheme {
  margin-top: 5px;
  font-family: var(--font-mono);
  font-size: var(--t-small);
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}

.rk-lift-aside {
  grid-column: 1 / -1;
  margin-top: 6px;
  display: flex;
  gap: 7px;
  align-items: baseline;
  font-size: var(--t-small);
  color: var(--text-secondary);
}

.rk-lift-aside i { font-size: var(--i-sm); color: var(--text-muted); flex: none; }
.rk-lift-aside.rk-ramp { font-family: var(--font-mono); }

/* What moved since last week, on the lift it moved on. */
.rk-tag {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 14px;
  border: 1px solid var(--accent);
  border-radius: var(--r-pill);
  background: var(--accent-bg);
  color: var(--accent);
  font-size: var(--t-micro);
  white-space: nowrap;
}

.rk-tag i { font-size: var(--i-sm); }


/* --------------------------------------------------------------------------
   Today: what the engine decided

   The differentiator, and until chip C2 it was engine notation printed at
   athletes verbatim. Every sentence here comes off `reasoning` on
   `/week/preview`, which C2 put beside the frozen `note` for exactly this. */

.rk-feed-group { overflow: hidden; }
.rk-feed-group + .rk-feed-group { margin-top: 16px; }

.rk-feed-head {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  align-items: baseline;
  flex-wrap: wrap;
  padding: 13px 16px;
}

.rk-feed-item {
  padding: 14px 16px;
  border-top: 1px solid var(--border);
}

.rk-feed-line {
  display: flex;
  justify-content: space-between;
  gap: 14px;
  align-items: baseline;
}

.rk-feed-why {
  margin-top: 7px;
  max-width: 58ch;
  font-size: var(--t-small);
  color: var(--text-secondary);
  text-wrap: pretty;
}

/* The verdict, in the vocabulary reckoner/reasoning.py writes. Tones are the
   ones /next-reasoning already draws, so the two screens name a decision the
   same colour: the action accent is a gain, relief is the success green and
   never the gain colour, a hold is neutral ink because nothing happened, and an
   ask is amber because it wants something from the athlete. */
.rk-verdict {
  display: inline-block;
  padding: 4px 10px;
  border-radius: var(--r-pill);
  font-size: var(--t-micro);
  letter-spacing: .02em;
  white-space: nowrap;
}

.rk-verdict[data-tone="gain"] { color: var(--accent); background: var(--accent-bg); }
.rk-verdict[data-tone="relief"] { color: var(--success); background: var(--success-bg); }
.rk-verdict[data-tone="ask"] { color: var(--warning); background: var(--warning-bg); }
.rk-verdict[data-tone="flat"] { color: var(--text-secondary); background: var(--surface-3); }

.rk-delta {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--text-secondary);
}

.rk-more { width: 100%; margin-top: 14px; }


/* --------------------------------------------------------------------------
   Today: what the training asks you to eat

   The design puts this above the feed in the right-hand pane. It reads
   `/nutrition/targets`, which follows the training load, so it belongs on the
   screen that says what today is rather than only on Fuel. */

.rk-fuel { padding: 16px 18px; margin-bottom: 20px; }

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

.rk-fuel-head i { font-size: var(--i-sm); margin-inline-end: 7px; color: var(--accent); }

.rk-kcal {
  font-size: var(--t-card);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

.rk-kcal small { font-size: var(--t-small); color: var(--text-secondary); }

.rk-macro {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 14px;
  padding: 12px 0;
  border-top: 1px solid var(--border);
}

.rk-macro:first-of-type { margin-top: 12px; }

.rk-macro-need {
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--t-lede);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}


/* --------------------------------------------------------------------------
   Log: how you woke up

   Three cards rather than the three chips Today uses, because this is the one
   moment the athlete is being asked rather than offered, and the difference
   between Rough and Primed is a sentence each rather than a word. */

.rk-ready-cards {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
  margin-top: 12px;
}

@media (min-width: 700px) {
  .rk-ready-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* A column rather than the button's default, and that is a fix rather than a
   preference. A `<button>` centres its content vertically in every engine, so
   three cards of equal height carrying four, four and five lines of copy put
   their three labels at three different heights. Measured in a browser at 1440:
   17px, 28px and 28px from the top of the card. Declaring the flow makes it
   17, 17, 17. */
.rk-ready-card {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  padding: 16px;
  min-height: var(--tap);
  text-align: left;
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  background: var(--surface-2);
  color: var(--text-primary);
  cursor: pointer;
  transition: background var(--motion), border-color var(--motion);
}

.rk-ready-card:hover { border-color: var(--border-strong); }

.rk-ready-card[aria-pressed="true"] {
  border-color: var(--accent);
  background: var(--accent-bg);
}

.rk-ready-card b { display: block; font-size: var(--t-lede); font-weight: 400; }

.rk-ready-card span {
  display: block;
  margin-top: 6px;
  font-size: var(--t-small);
  color: var(--text-secondary);
  line-height: 1.5;
}


/* --------------------------------------------------------------------------
   Log: one lift

   The card, then a row per set, then the effort question. A skipped lift keeps
   its rows and dims them: a skip is a decision the athlete can take back, and a
   card that collapsed would make taking it back a hunt. */

.rk-log-lift { margin-top: 16px; overflow: hidden; }

.rk-log-head { padding: 16px 18px; }

.rk-log-title {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 14px;
  flex-wrap: wrap;
}

.rk-log-name { font-size: var(--t-lede); }

.rk-log-pres {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}

.rk-log-note { margin-top: 6px; font-size: var(--t-small); color: var(--text-secondary); }
.rk-log-note.rk-ramp { font-family: var(--font-mono); }

.rk-log-lift[data-skipped] .rk-set,
.rk-log-lift[data-skipped] .rk-effort { opacity: .5; }

/* THE SET ROW. `1fr auto auto` is the design's grid and it is a requirement
   rather than a preference: the name and the two entries. Here the name column
   is the set's own number, and the two boxes hold their width whatever it is
   called, so Set 1 and Set 10 do not shuffle the inputs sideways. */
.rk-set {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  gap: 12px;
  padding: 12px 18px;
  border-top: 1px solid var(--border);
}

.rk-set-n {
  font-size: var(--t-small);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.rk-entry { display: flex; align-items: center; gap: 8px; }
.rk-entry span { font-size: var(--t-small); color: var(--text-secondary); }

/* At least 48px tall and 56px wide, which is `--tap` and the `.rk-tap` minimum.
   This is the one measurement on either screen that a thumb in a gym decides,
   so it is written against the token rather than as a number. The two widths
   below are floors over that minimum, not replacements for it: reps is two
   digits, a load is four and a decimal. */
.rk-entry input {
  min-height: var(--tap);
  min-width: 56px;
  padding: 0 8px;
  text-align: center;
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  background: var(--surface-0);
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: var(--t-lede);
  font-variant-numeric: tabular-nums;
}

.rk-entry input[aria-invalid="true"] { border-color: var(--alert); }

.rk-entry--reps input { width: 74px; }
.rk-entry--load input { width: 88px; }

/* The effort question, on the tinted foot of the card. */
.rk-effort {
  padding: 16px 18px;
  border-top: 1px solid var(--border);
  background: var(--surface-1);
}

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

.rk-seg { display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap; }

.rk-seg button {
  min-width: 56px;
  min-height: var(--tap);
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  background: var(--surface-2);
  color: var(--text-secondary);
  font-family: var(--font-mono);
  font-size: var(--t-lede);
  cursor: pointer;
  transition: background var(--motion), color var(--motion), border-color var(--motion);
}

.rk-seg button:hover { background: var(--surface-1); }

.rk-seg button[aria-pressed="true"] {
  border-color: var(--accent);
  background: var(--accent-bg);
  color: var(--text-primary);
}

.rk-mean { margin-top: 12px; max-width: 56ch; }

.rk-teach {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}

.rk-teach-row { display: flex; gap: 14px; align-items: baseline; padding: 7px 0; }

.rk-teach-row b {
  width: 24px;
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--t-body);
  font-weight: 400;
}


/* --------------------------------------------------------------------------
   Log: the note, and the bar that finishes the session */

.rk-note-card { margin-top: 16px; padding: 16px 18px; }

.rk-note-card textarea {
  width: 100%;
  margin-top: 10px;
  min-height: 88px;
  padding: 12px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  background: var(--surface-0);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: var(--t-body);
  resize: vertical;
}

/* Sticky rather than fixed, so it lives inside the 820px column and needs no
   arithmetic of its own. `.rk-col[data-sticky]` is what buys it the 120px of
   bottom padding, or the last lift would sit underneath it. A floating bar is
   one of the three things this system allows a shadow on. */
.rk-finish {
  position: sticky;
  bottom: 16px;
  z-index: 2;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 20px;
  padding: 14px 18px;
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-banner);
}

.rk-finish-say { flex: 1 1 200px; }

/* What landed, after the session is written. Success rather than the accent,
   because it is relief rather than an action. */
.rk-logged {
  margin-top: 20px;
  padding: 16px 18px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-left: 3px solid var(--success);
  border-radius: var(--r-card);
}

.rk-logged-head {
  display: flex;
  align-items: center;
  gap: 9px;
  color: var(--success);
}

.rk-logged p { margin-top: 6px; }
.rk-logged-links { display: flex; gap: 16px; margin-top: 12px; flex-wrap: wrap; }


/* --------------------------------------------------------------------------
   Where the shared blocks land inside these two screens

   The state banner and the inset empty state are drawn by js/states.js and know
   nothing about the screen they were handed to, which is the point of them. What
   a screen owes them is the space around them, and that belongs here rather than
   in a `style=` attribute on the node: an inline margin written by a module is a
   value nothing in this file can find, and section 1's whole argument is that a
   value nobody can find is a value that drifts. */

.rk-col > .rk-banner { margin-bottom: 24px; }

/* An inset empty state pads itself vertically. Inside a card it still needs the
   card's own side gutter, or the sentence sits flush against the hairline. */
.rk-session > .rk-state,
.rk-feed-pane .rk-state { padding-inline: 20px; }

.rk-macro-role { display: block; }
.rk-feed-verdict { margin-top: 6px; }

/* The day picker on Log. The design draws one training day because it draws one
   screenshot; the product schedules six, and a browser tab opened on a Tuesday
   to log Monday's session is ordinary rather than exotic. So it stays, quietly,
   above the session it selects. */
.rk-day-pick {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 20px;
  flex-wrap: wrap;
}

.rk-day-pick > span { font-size: var(--t-small); color: var(--text-secondary); }

.rk-day-pick select {
  flex: 1 1 220px;
  min-height: var(--tap);
  padding: 0 12px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  background: var(--surface-2);
  color: var(--text-primary);
  font-size: var(--t-body);
}


/* ==========================================================================
   6. THE FOUNDING-MEMBER BADGE
   ==========================================================================

   FM-52 chip C8, appended below everything C1 and C4 wrote, for the reason C4
   gives above: a rebase against another chip touching this file stays
   mechanical. Nothing above this line moved, and this section declares no
   tokens at all, because the badge is the mark plus the type scale and needed
   none.

   One badge, permanent, carrying the number and the date. No levels, no points,
   no leaderboard, nothing to climb, and no gold, no crown, no trophy: the grant
   is a fact about when somebody arrived rather than a prize, and it has to read
   as earned rather than bought. What draws it is the mark itself, which is the
   plate stack with its oxide top plate, so the one place oxide appears here is
   the place the rule already allows it. js/founder.js builds all of this.

   NOT DRAWN, ON PURPOSE: anything about recency. The delivered prototype's
   founder card carries a twelve-month keep-it rule and a green tick reading
   "Last session 3 days ago. Nothing to do." The grant is unconditional and
   nothing in the product may imply otherwise, so there is no class here for a
   status line beside the number, and adding one is the thing to argue about
   rather than the thing to style. */

.rk-founder {
  display: flex;
  gap: 20px;
  align-items: center;
  flex-wrap: wrap;
  padding: 22px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
}

/* The mark at the size the design draws it on the founder screen. `flex: none`
   because it is the one fixed thing in the row: the body reflows around it and
   the mark never squeezes. */
.rk-founder-mark {
  width: 84px;
  height: 84px;
  display: block;
  flex: none;
}

.rk-founder-body { min-width: 0; }

/* The number, at the card step. It is the largest thing in the block and it is
   deliberately not an h1: the badge is a component that appears inside screens
   that already have a heading, and a second h1 in a document is a heading list
   with two page titles in it. */
.rk-founder-n {
  font-size: var(--t-card);
  font-weight: 500;
  line-height: var(--lh-head);
  letter-spacing: var(--ls-head);
  color: var(--text-primary);
}

.rk-founder-name { margin-bottom: 4px; }
.rk-founder-at { margin-top: 6px; }

/* What the grant entitles. Three sentences, wrapped at a readable measure, and
   the third of them is the one that says it is not at risk. Full width of the
   card rather than beside the mark, because it is prose and the row above is a
   label. */
.rk-founder-terms {
  flex-basis: 100%;
  display: grid;
  gap: 8px;
  margin-top: 4px;
  max-width: 56ch;
  text-wrap: pretty;
}

/* The byline: a name, and after it the mark at 16px and the number. One
   component rather than a name with a badge appended, because the obvious
   composition draws the label twice for exactly the athletes it matters most
   for. See byline() in js/founder.js for that, since it is a behaviour rather
   than a style and cannot be fixed here.

   16px is the size the design says the mark has to survive at, so it is drawn at
   that size rather than at whatever a screen scales it to. */
.rk-byline {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}

.rk-byline-name { color: var(--text-primary); }

/* The number after the mark, at the small step, so the name is what is read and
   the grant is what is noticed second. */
.rk-founder-chip-n {
  font-size: var(--t-small);
  color: var(--text-secondary);
}

.rk-founder-chip-mark {
  width: 16px;
  height: 16px;
  display: block;
  flex: none;
}


/* ==========================================================================
   7. THE FEEDBACK SURFACES
   ==========================================================================

   FM-52 chip C9, appended below everything C1, C4, C3 and C8 wrote, for the
   reason C4 gives above: another chip is in this file at the same time, so a
   rebase should be an append rather than a merge. Nothing above this line
   moved, and this section declares no tokens at all.

   The board, the report form and the shipped feed. They are the first PUBLIC
   screens built on this layer, and public means two things here: the page is
   pinned dark in its own markup with no theme control, and the eyebrow takes
   the wider public tracking `.rk-eyebrow--public` declares in section 3
   (js/states.js passes `publicPage` and the class is applied there, so a page
   cannot be off by a colour through forgetting a modifier).

   ONE COLOUR IS HELD FOR DESIGN, and it is the interesting one. The delivered
   prototype draws "Not doing it" in a burgundy that this token set does not
   have. Adding a ninth colour is a decision rather than a fix, so the four
   status tones below map onto tokens that already exist and already clear AA on
   every surface: reading is muted, doing is the accent, done is success, and no
   is `--alert`. In this context `--alert` reads as a stop rather than as an
   error, which is the right note for a decision that was made deliberately and
   is being stated in public. If Design wants the burgundy it is one token and
   nothing below moves.

   The four tones are written once, here, and every status pill, count tile,
   card edge and trail dot reads `--tone` off the same attribute. So a fifth
   status would be one line in this block and one line in js/feedback.js. */

[data-tone="reading"] { --tone: var(--text-muted); }
[data-tone="doing"] { --tone: var(--accent); }
[data-tone="done"] { --tone: var(--success); }
[data-tone="no"] { --tone: var(--alert); }


/* --------------------------------------------------------------------------
   The status pill

   Drawn as an outline in the tone rather than a fill, because four filled
   badges down a page of cards is four colours competing with the words. The
   exception is not made for "Not doing it": a no that shouted would be a no
   that reads as a telling-off, and the point of publishing it is the opposite. */

.rk-bstatus {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: none;
  padding: 6px 11px;
  border: 1px solid var(--tone, var(--text-muted));
  border-radius: var(--r-pill);
  color: var(--tone, var(--text-muted));
  font-size: var(--t-micro);
  letter-spacing: .06em;
  text-transform: uppercase;
  white-space: nowrap;
}

.rk-bstatus i { font-size: var(--i-sm); }


/* --------------------------------------------------------------------------
   The four counts across the top */

.rk-bcounts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  margin-top: 28px;
}

@media (min-width: 700px) {
  .rk-bcounts { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

.rk-bcount {
  padding: 16px 18px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
}

.rk-bcount-head { display: flex; align-items: center; gap: 9px; }
.rk-bcount-head i { color: var(--tone, var(--text-muted)); font-size: var(--i-lg); }
.rk-bcount-n { margin-top: 6px; font-variant-numeric: tabular-nums; }


/* --------------------------------------------------------------------------
   Filters, and the count beside them */

.rk-bfilters { margin-top: 20px; align-items: center; }
.rk-bfilter-n { margin-inline-start: auto; font-variant-numeric: tabular-nums; }

.rk-banner + .rk-bfilters { margin-top: 20px; }
.rk-bcounts + .rk-banner { margin-top: 16px; }


/* --------------------------------------------------------------------------
   A card on the board

   The left edge in the status tone is the same component the state banner is:
   a hairline card whose coloured edge is what tells it apart at a glance. See
   the HELD note above `.rk-banner` in section 4, which this inherits rather
   than reopens. The rule it does not break is the one about CARDS, which carry
   content and stay flat: `.rk-card` is untouched and `.rk-scard` on the shipped
   feed uses it plain. */

.rk-bgrid { display: grid; gap: 12px; margin-top: 20px; }

.rk-bcard {
  padding: 20px 22px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-left: 3px solid var(--tone, var(--text-muted));
  border-radius: var(--r-card);
}

.rk-bcard-head {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  align-items: baseline;
  flex-wrap: wrap;
}

.rk-bcard-open { color: var(--text-primary); }
.rk-bcard-open:hover { text-decoration: none; }
.rk-bcard-open:hover .rk-bcard-title { color: var(--accent); }
.rk-bcard-title { max-width: 62ch; text-wrap: pretty; transition: color var(--motion); }
.rk-bcard-note { margin-top: 10px; max-width: 76ch; text-wrap: pretty; }


/* --------------------------------------------------------------------------
   Who asked, and when */

.rk-bfoot {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}


/* --------------------------------------------------------------------------
   Voting

   Two numbers we read, not a conversation. The share bar is drawn only when
   somebody has voted, because a bar at 0/0 is a shape that means nothing and
   invites a reader to work out what an empty one is saying. */

.rk-bvotes { margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--border); }

.rk-bshare {
  display: flex;
  height: 5px;
  border-radius: 3px;
  overflow: hidden;
  background: var(--border);
}

.rk-bshare-up { background: var(--accent); }
.rk-bshare-down { background: var(--alert); }

.rk-bvote-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 12px;
}

.rk-bvote {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 0 14px;
  min-height: var(--tap);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--text-secondary);
  font-size: var(--t-small);
  cursor: pointer;
  transition: background var(--motion), color var(--motion), border-color var(--motion);
}

.rk-bvote:hover { background: var(--surface-1); }
.rk-bvote i { font-size: var(--i-md); }
.rk-bvote .rk-num { color: var(--text-primary); }

/* The one you chose. Filled in its own tint rather than in the accent for both,
   so "Not for me" pressed does not read as an error state. */
.rk-bvote[aria-pressed="true"] { color: var(--text-primary); }
.rk-bvote[data-dir="up"][aria-pressed="true"] {
  border-color: var(--accent);
  background: var(--accent-bg);
}
.rk-bvote[data-dir="up"][aria-pressed="true"] i,
.rk-bvote[data-dir="up"][aria-pressed="true"] .rk-num { color: var(--accent); }
.rk-bvote[data-dir="down"][aria-pressed="true"] {
  border-color: var(--alert);
  background: var(--alert-bg);
}
.rk-bvote[data-dir="down"][aria-pressed="true"] i,
.rk-bvote[data-dir="down"][aria-pressed="true"] .rk-num { color: var(--alert); }

/* The line an athlete may attach. It is private until we publish it, and the
   note under it says so, so this control is deliberately quiet: it is not a
   comment box and should not look like one. */
.rk-bwhy {
  display: block;
  width: 100%;
  margin-top: 14px;
  padding: 12px;
  background: var(--surface-1);
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  font-size: var(--t-body);
  resize: vertical;
}

.rk-bwhy + p { margin-top: 8px; max-width: 62ch; text-wrap: pretty; }

/* Ten people asking after a no, which reopens it. Accent rather than alert:
   it is the good news on a declined item. */
.rk-bagain { margin-top: 12px; max-width: 70ch; color: var(--accent); }

.rk-bask {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
  margin-top: 28px;
  padding: 24px;
  border-color: var(--accent);
}

.rk-bask p + p { margin-top: 6px; max-width: 60ch; }


/* --------------------------------------------------------------------------
   One item in full */

.rk-bback {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: var(--tap);
  font-size: var(--t-small);
}

.rk-bhead {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 16px;
  flex-wrap: wrap;
}

.rk-bhead .rk-head > h1 { max-width: 24ch; text-wrap: pretty; }
.rk-bhead .rk-bstatus { margin-top: 6px; }

.rk-bnote { margin-top: 22px; max-width: 66ch; color: var(--text-primary); text-wrap: pretty; }
.rk-bvote-card { margin-top: 26px; padding: 20px 22px; }
.rk-bvote-card .rk-bvotes { margin-top: 0; padding-top: 0; border-top: 0; }

.rk-btrail-head { margin-top: 36px; }

/* The trail. A dot in the status tone, the status, the date, and the sentence.
   An ordered list because it is one: the order is the point. */
.rk-btrail { margin: 14px 0 0; padding: 0; list-style: none; }

.rk-btrail-step {
  display: grid;
  grid-template-columns: 14px 1fr;
  gap: 16px;
  padding: 16px 0;
  border-top: 1px solid var(--border);
}

.rk-btrail-step::before {
  content: "";
  width: 9px;
  height: 9px;
  margin-top: 8px;
  border-radius: 50%;
  background: var(--tone, var(--text-muted));
}

/* Both of these are the SECOND column, named rather than left to flow. The dot
   is drawn by ::before, so it is grid item one and the two text blocks are items
   two and three: without this the sentence lands in row two of the 14px column
   and wraps one word per line. */
.rk-btrail-line {
  grid-column: 2;
  display: flex;
  gap: 10px;
  align-items: baseline;
  flex-wrap: wrap;
}

.rk-btrail-step > p { grid-column: 2; margin-top: 6px; max-width: 64ch; text-wrap: pretty; }

.rk-bquotes { display: grid; gap: 12px; margin-top: 14px; }

.rk-bquote {
  margin: 0;
  padding: 16px 18px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--r-card);
}

.rk-bquote blockquote { margin: 0; max-width: 62ch; text-wrap: pretty; }
.rk-bquote figcaption { margin-top: 9px; }


/* --------------------------------------------------------------------------
   Report something

   Two forms behind two buttons, and the fields are rebuilt rather than shown
   and hidden, so a field an athlete cannot see is a field that does not exist.
   Every control is at the 48px thumb target: this is a page somebody reaches
   for on a phone, in a gym, at the moment something has just gone wrong. */

.rk-rlede { margin-top: 14px; max-width: 56ch; text-wrap: pretty; }

.rk-rkinds { display: flex; gap: 10px; margin-top: 28px; flex-wrap: wrap; }

.rk-rkind {
  flex: 1;
  min-width: 200px;
  min-height: var(--tap);
  padding: 16px 18px;
  text-align: start;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  color: var(--text-secondary);
  font-size: var(--t-lede);
  cursor: pointer;
  transition: background var(--motion), color var(--motion), border-color var(--motion);
}

.rk-rkind:hover { background: var(--surface-1); }

.rk-rkind[aria-pressed="true"] {
  background: var(--accent-bg);
  border-color: var(--accent);
  color: var(--text-primary);
}

.rk-rbody { margin-top: 16px; padding: 22px; }
.rk-rform { display: grid; gap: 18px; }
.rk-rfield { display: block; }
.rk-rlabel { display: block; margin-bottom: 8px; color: var(--text-primary); }
.rk-rfield p { margin-top: 7px; max-width: 60ch; }

.rk-rtext,
.rk-rselect {
  display: block;
  width: 100%;
  padding: 12px;
  background: var(--surface-1);
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  font-size: var(--t-body);
}

.rk-rtext { resize: vertical; }
.rk-rselect { min-height: var(--tap); }

/* Two controls side by side, or one filling the row. `auto-fit` rather than
   `1fr 1fr` because the watch field is dropped for an account with no paired
   watch, and a fixed two-column grid leaves that account looking at a
   half-width control beside an empty half. */
.rk-rtwo { display: grid; gap: 14px; }

@media (min-width: 700px) {
  .rk-rtwo { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
}

.rk-rsend { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }

/* Who this will be sent as, and it is the PUBLIC answer rather than the
   athlete's own name: an athlete who has not answered the display-name question
   sees "Founding member 07" here and learns the fact before they send. */
.rk-rwho { display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; }

.rk-rfine { margin-top: 16px; max-width: 64ch; text-wrap: pretty; }
.rk-rafter { display: flex; gap: 12px; margin-top: 22px; flex-wrap: wrap; }
.rk-head + .rk-banner { margin-top: 22px; }


/* --------------------------------------------------------------------------
   Shipped

   A plain `.rk-card` with no coloured edge, unlike the board. Everything on
   this page has the same status, so an edge repeated down every entry would be
   carrying no information at all. */

.rk-slede { margin-top: 16px; max-width: 56ch; text-wrap: pretty; }
.rk-sfeed { display: grid; gap: 14px; margin-top: 24px; }
.rk-scard { padding: 24px; }

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

.rk-scard-flag { display: inline-flex; align-items: center; gap: 6px; color: var(--success); font-size: var(--t-small); }
.rk-scard-flag i { font-size: var(--i-md); }
.rk-scard-title { margin-top: 10px; text-wrap: pretty; }
.rk-scard-note { margin-top: 10px; max-width: 72ch; text-wrap: pretty; }
.rk-scard-link { margin-inline-start: auto; }
.rk-sfine { margin-top: 20px; max-width: 64ch; text-wrap: pretty; }


/* --------------------------------------------------------------------------
   The public top bar

   The same `.rk-top` C3 drew, carrying THREE links where the app bar carries
   two. That is one too many for a 390px phone: the nav and the account chip run
   off the right edge and the last link is unreachable.

   Narrow first, like everything else in this file, so there is no max-width
   query undoing a min-width one. On a phone the bar is two rows with the nav on
   its own, scrolling sideways if a fourth page is ever added; from 700px it is
   the single row C3 drew and nothing about it has changed. Scoped to
   `[data-public]`, the attribute the public pages already carry, so the
   signed-in bar is untouched. */

[data-public] .rk-top-inner { flex-wrap: wrap; gap: 12px; }

[data-public] .rk-nav {
  order: 3;
  width: 100%;
  margin-inline-start: 0;
  overflow-x: auto;
  scrollbar-width: none;
}

[data-public] .rk-nav a { white-space: nowrap; }
[data-public] .rk-nav::-webkit-scrollbar { display: none; }
[data-public] .rk-who { margin-inline-start: auto; }

@media (min-width: 700px) {
  [data-public] .rk-top-inner { flex-wrap: nowrap; gap: 20px; }
  [data-public] .rk-nav {
    order: 0;
    width: auto;
    margin-inline-start: auto;
    overflow-x: visible;
  }
}

/* ==========================================================================
   8. THE REMAINING SIGNED-IN SCREENS
   ==========================================================================

   FM-52 chip C5, appended below everything C1, C4, C3, C8 and C9 wrote, for
   the reason each of those gave in turn: more than one chip is in this file at
   a time, so a rebase should be an append rather than a merge. Nothing above
   this line moved.

   IT IS SECTION 8 BECAUSE C9 GOT HERE FIRST. Both chips were open together and
   both wrote a section 7, which is exactly the collision the note above
   predicts and exactly the resolution it asks for: the feedback surfaces stay
   where they landed and this follows them, rather than the two being merged
   into each other. The only thing that moved is this number.

   Eight screens land here: the week, history, where you stand, fuel, the
   exercise library, setup, account and your watches. What they have in common
   is more interesting than what they do not, so this section opens with the
   shared blocks (the account menu, the settings row, the switch, the modal) and
   only then goes screen by screen. A screen inventing its own version of one of
   those is the thing this arrangement exists to stop, exactly as js/states.js
   stops a screen inventing its own error page.

   THE ONE PLACE THIS SECTION DRAWS A SHADOW is the account menu and the confirm
   dialog, through `--shadow-overlay`. Section 1 allows that and only that: a
   floating overlay is not depth on the page, it is a thing in front of it, and
   the tint-plus-hairline system has nothing to say about something that is not
   sitting on a surface at all.

   THE SIX TIER BANDS are the one new colour idea, and they are declared as
   theme-STABLE values rather than as tokens that flip. A tier is the same tier
   in either theme, and a ramp that inverted would put Elite at the pale end for
   half the readers. They are backgrounds with nothing drawn on them except the
   position marker, which carries its own outline for exactly that reason: a
   marker inked in --text-primary vanishes into chalk at one end of the ramp in
   dark theme and into pine at the other in light. */

:root {
  /* Untrained through Elite: chalk climbing to deep teal. The design package's
     values. History overlays the same thresholds as hairlines rather than
     fills, so nothing else reads a colour from here. */
  --band-0: #d4cec0;
  --band-1: #a9ddc7;
  --band-2: #63c39c;
  --band-3: #17a074;
  --band-4: #10684f;
  --band-5: #0a3b2d;
}


/* --------------------------------------------------------------------------
   The shell: the bar, and the account menu behind the chip

   C3 shipped the bar as markup with a name plate in it and said the menu
   belonged to the chip that owned the screens it leads to. This is that chip, so
   the menu lands here, and the bar is built by js/screen-chrome.js from one
   table rather than copied into ten pages. */

.rk-nav a span { white-space: nowrap; }

/* SIX ENTRIES DO NOT FIT ACROSS A PHONE, and what happens then is a decision
   rather than an accident. Written narrow first, like everything else in this
   file: the bar scrolls sideways at a phone width and the account chip is the
   disc alone, and at 700px the name comes back and the nav is allowed to wrap.

   `min-width: 0` is the load-bearing half. Without it a flex item refuses to
   shrink below its content, so the nav pushes the bar wider than the viewport
   and the WHOLE PAGE scrolls sideways rather than the one row that should.
   Measured at 375: the document was 946px wide. */
.rk-nav {
  min-width: 0;
  flex-wrap: nowrap;
  overflow-x: auto;
  margin-inline-start: 0;
}

.rk-top-inner { gap: 10px; }
.rk-mark { flex: none; }
.rk-who-name { display: none; }

@media (min-width: 700px) {
  .rk-top-inner { gap: 20px; }
  .rk-nav { flex-wrap: wrap; overflow-x: visible; margin-inline-start: auto; }
  .rk-who-name { display: inline; }
}

.rk-account { position: relative; }

.rk-who {
  cursor: pointer;
  background: transparent;
  text-align: left;
}

.rk-who:hover { background: var(--surface-1); }
.rk-who i { font-size: var(--i-md); color: var(--text-muted); }
.rk-who-name { max-width: 14ch; overflow: hidden; text-overflow: ellipsis; }

.rk-disc--lg {
  width: 48px;
  height: 48px;
  font-size: var(--t-lede);
}

/* The menu. Hidden rather than absent so the athlete's own name and the theme
   control are in the document from the first paint, which keeps the tab order
   stable and means opening it is a class change rather than a build. */
.rk-menu {
  position: absolute;
  z-index: 20;
  top: calc(100% + 8px);
  inset-inline-end: 0;
  width: min(300px, calc(100vw - 2 * var(--gutter)));
  padding: 6px;
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-overlay);
  display: none;
}

.rk-account[data-open="true"] .rk-menu { display: block; }

.rk-menu-who {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 12px 14px;
}

.rk-menu-who > div { min-width: 0; }

.rk-menu-theme {
  display: flex;
  gap: 4px;
  padding: 0 6px 10px;
  border-bottom: 1px solid var(--border);
}

.rk-menu-theme-btn {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 40px;
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: transparent;
  color: var(--text-secondary);
  font-size: var(--t-small);
  cursor: pointer;
  transition: background var(--motion), color var(--motion), border-color var(--motion);
}

.rk-menu-theme-btn i { font-size: var(--i-sm); }
.rk-menu-theme-btn:hover { background: var(--surface-1); }

.rk-menu-theme-btn[aria-pressed="true"] {
  border-color: var(--accent);
  background: var(--accent-bg);
  color: var(--text-primary);
}

.rk-menu-list { display: block; padding-top: 6px; }

.rk-menu-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 0 12px;
  min-height: var(--tap);
  border: 0;
  border-radius: var(--r);
  background: transparent;
  color: var(--text-primary);
  font-size: var(--t-small);
  text-align: left;
  cursor: pointer;
  transition: background var(--motion);
}

.rk-menu-item:hover { background: var(--surface-1); text-decoration: none; }
.rk-menu-item i { font-size: var(--i-lg); color: var(--text-muted); flex: none; }
.rk-menu-item[aria-current="page"] { color: var(--accent); }
.rk-menu-item[aria-current="page"] i { color: var(--accent); }

.rk-menu-out {
  margin-top: 6px;
  border-top: 1px solid var(--border);
  border-radius: 0 0 var(--r) var(--r);
  color: var(--text-secondary);
}


/* --------------------------------------------------------------------------
   The settings row, the switch, and the modal

   Setup, account and the week's three life controls are all the same shape:
   what it is, what it says now, one line of consequence, and one control on the
   right. Written once here, because a settings screen where each row is styled
   separately is a settings screen where three of them drift. */

.rk-rows { overflow: hidden; }
.rk-rows-head { padding: 16px 18px 14px; }
.rk-rows-head > p { margin-top: 8px; max-width: 62ch; text-wrap: pretty; }

.rk-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 6px 16px;
  align-items: center;
  padding: 16px 18px;
  border-top: 1px solid var(--border);
}

.rk-row-what { min-width: 0; }
.rk-row-what > p { margin-top: 3px; }
.rk-row-what > p + p { margin-top: 4px; max-width: 54ch; text-wrap: pretty; }

/* A value long enough to be a paragraph gets the whole width. Equipment is a
   commercial gym's entire inventory, and squeezing thirty words into a value
   column gives fifteen two-word lines. */
.rk-row[data-wide] { grid-template-columns: 1fr; }
.rk-row[data-wide] > button { justify-self: start; }

/* The editor, the confirm and the two-step forms all open UNDER the row they
   belong to rather than replacing it, so the thing being changed stays on
   screen while it is being changed. */
.rk-row-edit {
  grid-column: 1 / -1;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}

.rk-row-edit > p { max-width: 58ch; text-wrap: pretty; }
.rk-row-edit > p + p { margin-top: 8px; }
.rk-row[data-open] { background: var(--surface-1); }

.rk-said-value { color: var(--text-primary); }

/* An unanswered field, and which kind of gap it is. Two sentences rather than
   one: gate 1 is something Reckoner cannot build a program without, everything
   else is an offer, and body fat is the one nothing ever asked for. */
.rk-gap {
  display: inline-block;
  margin-top: 4px;
  padding: 3px 10px;
  border-radius: var(--r-pill);
  font-size: var(--t-micro);
  letter-spacing: .06em;
  text-transform: uppercase;
}

.rk-gap[data-tone="need"] { color: var(--warning); background: var(--warning-bg); }
.rk-gap[data-tone="opt"] { color: var(--text-secondary); background: var(--surface-3); }

/* The switch. A real button with role=switch, so it is one tab stop and the
   state is announced, which a styled checkbox is not without three attributes
   nobody adds by hand. */
.rk-switch {
  width: 52px;
  height: 30px;
  flex: none;
  padding: 3px;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  border: 0;
  border-radius: var(--r-pill);
  background: var(--border-strong);
  cursor: pointer;
  transition: background var(--motion);
}

.rk-switch[aria-checked="true"] { background: var(--accent); justify-content: flex-end; }

.rk-switch-knob {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--surface-2);
}

/* What the page says after a write landed. Success rather than the accent,
   because it is relief rather than an action, and it never appears after a
   change the athlete declined. */
.rk-said {
  display: flex;
  align-items: baseline;
  gap: 9px;
  margin-top: 18px;
  padding: 12px 16px;
  background: var(--success-bg);
  border-radius: var(--r);
  color: var(--text-primary);
  font-size: var(--t-small);
}

.rk-said i { color: var(--success); font-size: var(--i-md); }

/* The confirm. The one dialog on the site, and it exists for exactly one
   question: an edit that would end the training block. */
.rk-modal {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 12vh var(--gutter) var(--gutter);
  overflow: auto;
  background: rgba(12, 11, 8, .72);
}

.rk-modal-box {
  width: 100%;
  max-width: 520px;
  padding: 22px;
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-overlay);
}

.rk-modal-box > p { margin-top: 12px; max-width: 52ch; text-wrap: pretty; }
.rk-confirm-cost { padding: 12px 14px; }
.rk-modal-actions { display: flex; gap: 10px; margin-top: 20px; flex-wrap: wrap; }

/* Destructive. Outlined rather than filled at rest, filled only inside a confirm
   the athlete has already opened, so the red on the page is a warning and the
   red under a thumb is the commitment. */
.rk-btn--danger {
  background: var(--alert);
  border-color: var(--alert);
  color: var(--surface-2);
}

.rk-btn--danger:hover { background: var(--alert); filter: brightness(1.06); }

.rk-btn--danger-quiet { border-color: var(--alert); color: var(--alert); }
.rk-btn--danger-quiet:hover { background: var(--alert-bg); }

.rk-chip--quiet { border-color: var(--border); color: var(--text-muted); }

/* Shared small furniture. */
.rk-h2 { margin: 32px 0 12px; }
.rk-foot { margin-top: 16px; max-width: 64ch; text-wrap: pretty; }
.rk-lede-inline { font-size: var(--t-lede); }

.rk-tiles {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
  margin-top: 12px;
}

@media (min-width: 700px) {
  .rk-tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.rk-tile { padding: 16px 18px; background: var(--surface-1); }
.rk-tile > p { margin-top: 3px; max-width: 40ch; }

.rk-tile-value {
  margin-top: 6px;
  font-size: var(--t-card);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}


/* --------------------------------------------------------------------------
   The week

   Two panes at a laptop width: what the engine decided on the left, the three
   life controls on the right. The design's ratio, and the same arrangement
   Today uses, so an athlete moving between them is not re-learning a layout. */

.rk-week-panes {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
  margin-top: 26px;
  align-items: start;
}

@media (min-width: 1100px) {
  .rk-week-panes { grid-template-columns: 1.4fr 1fr; }
}

.rk-week-main > * + * { margin-top: 16px; }

.rk-advance { padding: 20px; }

.rk-advance-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 20px;
  flex-wrap: wrap;
}

.rk-advance-head p { margin-top: 6px; }
.rk-advance-head button { white-space: nowrap; }
.rk-advance-say { margin-top: 16px; padding: 12px 14px; max-width: 62ch; }
.rk-advance .rk-field-error { margin-top: 12px; }

.rk-week-reads { padding: 20px; }
.rk-week-reads > .rk-lede { margin-top: 10px; max-width: 60ch; text-wrap: pretty; }

.rk-week-day { overflow: hidden; }

.rk-week-day-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 14px 18px;
}

.rk-week-day-when { margin-inline-start: 8px; }

.rk-week-lift { padding: 14px 18px; border-top: 1px solid var(--border); }

.rk-week-lift-line {
  display: flex;
  justify-content: space-between;
  gap: 14px;
  align-items: baseline;
}

.rk-week-lift > .rk-verdict { margin-top: 6px; }

.rk-week-why {
  margin-top: 7px;
  max-width: 58ch;
  font-size: var(--t-small);
  color: var(--text-secondary);
  text-wrap: pretty;
}

.rk-week-day-foot {
  padding: 12px 18px;
  border-top: 1px solid var(--border);
  background: var(--surface-1);
}

/* The three life controls. */
.rk-switches { overflow: hidden; }
.rk-switches-head { padding: 16px 18px; }
.rk-switches-head > p { margin-top: 8px; max-width: 46ch; }

.rk-switch-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 6px 14px;
  align-items: start;
  padding: 16px 18px;
  border-top: 1px solid var(--border);
}

.rk-switch-what > .rk-eyebrow { margin-top: 3px; }
.rk-switch-body { grid-column: 1 / -1; margin-top: 4px; max-width: 46ch; text-wrap: pretty; }

.rk-switch-ask {
  grid-column: 1 / -1;
  margin-top: 12px;
  padding: 12px 14px;
  background: var(--surface-1);
  border-radius: var(--r);
}

.rk-switch-ask > .rk-chips { margin-top: 10px; }
.rk-switch-err { padding: 0 18px 16px; }


/* --------------------------------------------------------------------------
   History

   Three tabs, and the fourth (volume by muscle) is not built because nothing on
   the API answers it. See the note at the top of js/screen-history.js. */

.rk-tabs { display: flex; gap: 6px; margin-top: 20px; flex-wrap: wrap; }

.rk-tab {
  padding: 0 14px;
  min-height: var(--tap);
  border: 0;
  border-radius: var(--r);
  background: transparent;
  color: var(--text-secondary);
  font-size: var(--t-small);
  cursor: pointer;
  transition: background var(--motion), color var(--motion);
}

.rk-tab:hover { background: var(--surface-1); }

.rk-tab[aria-selected="true"] {
  background: var(--accent-bg);
  color: var(--text-primary);
  box-shadow: inset 0 -2px 0 var(--accent);
}

.rk-tabpanel { margin-top: 18px; }
.rk-tabpanel > div > * + * { margin-top: 12px; }

.rk-block {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 18px 22px;
}

.rk-block p { margin-top: 4px; max-width: 52ch; }
.rk-pips { display: flex; gap: 5px; flex: none; }

.rk-pip {
  width: 26px;
  height: 8px;
  border-radius: var(--r-pill);
  background: var(--surface-3);
}

.rk-pip[data-state="done"] { background: var(--accent); }
.rk-pip[data-state="now"] { background: var(--accent); opacity: .45; }

.rk-sess { overflow: hidden; }

.rk-sess-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 16px 22px;
}

.rk-sess-title { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.rk-sess-note { padding: 0 22px 14px; max-width: 60ch; }
.rk-sess[data-superseded] { opacity: .72; }

.rk-sess-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 6px 14px;
  align-items: center;
  padding: 12px 22px;
  border-top: 1px solid var(--border);
}

.rk-sess-row[data-skipped] { opacity: .55; }
.rk-sess-row > p { grid-column: 1 / -1; }
.rk-sess-name { display: flex; align-items: center; gap: 10px; min-width: 0; flex-wrap: wrap; }

.rk-sess-figs {
  display: flex;
  gap: 16px;
  align-items: baseline;
  font-variant-numeric: tabular-nums;
}

/* The personal-best mark: a ring rather than a filled dot, and the same ring
   the charts draw, so one shape means one thing across the whole screen. */
.rk-pb {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--accent);
  font-size: var(--t-micro);
  letter-spacing: .08em;
}

.rk-pb-ring {
  width: 9px;
  height: 9px;
  border: 2px solid var(--accent);
  border-radius: 50%;
}

.rk-record { color: var(--accent); }

.rk-lane-group { overflow: hidden; }

.rk-lane-toggle {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 16px 22px;
  border: 0;
  background: transparent;
  color: var(--text-primary);
  text-align: left;
  cursor: pointer;
  transition: background var(--motion);
}

.rk-lane-toggle:hover { background: var(--surface-1); }
.rk-lane-toggle i { font-size: var(--i-md); color: var(--text-muted); }
.rk-lane-day { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }

.rk-lane { padding: 16px 22px 20px; border-top: 1px solid var(--border); }

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

/* The chart. A fixed height rather than an aspect ratio, because the series is
   what varies and the box should not: two lanes side by side with different
   point counts have to be comparable at a glance. */
/* A label column and a plot area, rather than labels hung in the plot's own
   corners. In the corner the high one sits exactly on top of the first reading,
   which is a point somebody is trying to look at. */
.rk-chart {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 12px;
  height: 90px;
  margin-top: 14px;
}

.rk-chart-axis {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  text-align: end;
  white-space: nowrap;
}

.rk-chart-plot { position: relative; grid-column: -2 / -1; }

.rk-chart-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

.rk-chart-line { stroke: var(--accent); stroke-width: 1.5; }

/* The points are HTML rather than SVG circles, and the reason is in the note
   above `plot` in js/screen-history.js: the polyline's box is stretched with
   `preserveAspectRatio="none"`, which is right for a stroke and turns a circle
   into an ellipse. Positioned by percentage over the same box, so the two agree
   about where a reading sits, and centred on their own point rather than hung
   off its top left. */
.rk-chart-dot,
.rk-chart-pb {
  position: absolute;
  border-radius: 50%;
  transform: translate(-50%, -50%);
}

.rk-chart-dot { width: 7px; height: 7px; background: var(--accent); }

/* A personal best. The same ring the session ledger draws, so one shape means
   one thing across the whole screen, filled with the card's own surface so the
   line does not run through the middle of it. */
.rk-chart-pb {
  width: 13px;
  height: 13px;
  border: 2px solid var(--accent);
  background: var(--surface-2);
}


.rk-chart-foot {
  display: flex;
  justify-content: space-between;
  margin-top: 8px;
  border-top: 1px solid var(--border);
  padding-top: 8px;
}

.rk-chart-only { margin-top: 10px; max-width: 54ch; text-wrap: pretty; }

.rk-body-card { padding: 22px; }
.rk-body-card > .rk-fig-value { margin-top: 8px; }
.rk-body-card > p { margin-top: 8px; max-width: 56ch; }


/* --------------------------------------------------------------------------
   Where you stand

   The bands are the one ramp on the site. They do not flip with the theme,
   because a tier is the same tier in either. */

.rk-basis {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
  padding: 18px;
  margin-top: 22px;
  background: var(--surface-1);
}

.rk-basis-say { flex-basis: 100%; max-width: 62ch; text-wrap: pretty; }

/* Two mutually exclusive answers, joined into one control so the pair reads as
   one question rather than as two buttons that happen to be adjacent. */
.rk-seg2 {
  display: flex;
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  overflow: hidden;
}

.rk-seg2 button {
  min-height: var(--tap);
  padding: 0 15px;
  border: 0;
  background: transparent;
  color: var(--text-secondary);
  font-size: var(--t-small);
  cursor: pointer;
  transition: background var(--motion), color var(--motion);
}

.rk-seg2 button:hover { background: var(--surface-2); }

.rk-seg2 button[aria-pressed="true"] {
  background: var(--btn-bg);
  color: var(--btn-fg);
}

.rk-stand-lift { padding: 22px; }
.rk-stand-lift[data-provisional] { border-style: dashed; }

.rk-stand-head {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  align-items: baseline;
  flex-wrap: wrap;
}

.rk-stand-set { margin-inline-start: 10px; }
.rk-stand-fig { display: flex; align-items: baseline; gap: 12px; margin-top: 14px; flex-wrap: wrap; }
.rk-stand-next { margin-top: 14px; max-width: 60ch; text-wrap: pretty; }

.rk-tier {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-pill);
  font-size: var(--t-micro);
  letter-spacing: .1em;
  text-transform: uppercase;
  white-space: nowrap;
}

.rk-tier-dot { width: 9px; height: 9px; border-radius: 2px; }

.rk-track {
  position: relative;
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  height: 14px;
  margin-top: 30px;
  border-radius: var(--r);
  overflow: visible;
}

.rk-track > .rk-band:first-child { border-radius: var(--r) 0 0 var(--r); }
.rk-track > .rk-band:nth-child(6) { border-radius: 0 var(--r) var(--r) 0; }

/* The marker. Outlined against the surface rather than shadowed: a shadow is
   the one depth this system does not draw outside a floating overlay, and an
   outline is what makes the marker readable at both ends of a ramp that runs
   from chalk to near black. */
.rk-track-you {
  position: absolute;
  top: -7px;
  bottom: -7px;
  width: 4px;
  margin-inline-start: -2px;
  border-radius: 2px;
  background: var(--text-primary);
  outline: 2px solid var(--surface-2);
}

.rk-band[data-band="0"], .rk-tier[data-band="0"] .rk-tier-dot { background: var(--band-0); }
.rk-band[data-band="1"], .rk-tier[data-band="1"] .rk-tier-dot { background: var(--band-1); }
.rk-band[data-band="2"], .rk-tier[data-band="2"] .rk-tier-dot { background: var(--band-2); }
.rk-band[data-band="3"], .rk-tier[data-band="3"] .rk-tier-dot { background: var(--band-3); }
.rk-band[data-band="4"], .rk-tier[data-band="4"] .rk-tier-dot { background: var(--band-4); }
.rk-band[data-band="5"], .rk-tier[data-band="5"] .rk-tier-dot { background: var(--band-5); }

.rk-track-scale {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 4px;
  margin-top: 10px;
}

.rk-track-scale span {
  font-size: var(--t-micro);
  color: var(--text-secondary);
  text-align: center;
}

.rk-track-scale span[data-on] { color: var(--text-primary); font-weight: 500; }

.rk-stand-rest { padding: 22px; }
.rk-stand-rest > p { margin-top: 8px; max-width: 60ch; text-wrap: pretty; }

.rk-stand-rest-row {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 11px 0;
  border-top: 1px solid var(--border);
}

.rk-stand-rest-row + p { margin-top: 14px; }


/* --------------------------------------------------------------------------
   Fuel

   The target card is Today's, at full width. The two prose cards under it are
   the part the design spends most of the screen on, and they stay prose: the
   point they make is that the evidence is a RANGE and Reckoner picks inside it,
   which a dial pointing at one number would contradict. */

.rk-fuel-target { padding: 18px; margin-top: 22px; }
.rk-fuel-target + * { margin-top: 16px; }

.rk-explain, .rk-working, .rk-day { margin-top: 16px; }
.rk-explain { padding: 20px; background: var(--surface-1); }
.rk-explain > p { margin-top: 10px; max-width: 66ch; text-wrap: pretty; }

.rk-working { padding: 18px; }
.rk-working-line { margin-top: 10px; color: var(--text-secondary); }

.rk-day { overflow: hidden; }
.rk-day > .rk-fuel-head { padding: 16px 18px; }

.rk-meal { border-top: 1px solid var(--border); }

.rk-meal-head {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 5px 14px;
  align-items: center;
  width: 100%;
  padding: 15px 18px;
  border: 0;
  background: transparent;
  color: var(--text-primary);
  text-align: left;
  cursor: pointer;
  transition: background var(--motion);
}

.rk-meal-head:hover { background: var(--surface-1); }
.rk-meal-what { min-width: 0; display: grid; gap: 4px; }
.rk-meal-right { display: flex; align-items: center; gap: 12px; }
.rk-meal-right i { font-size: var(--i-lg); color: var(--text-muted); }

.rk-meal-parts { padding: 0 18px 16px; }

.rk-meal-part {
  display: flex;
  justify-content: space-between;
  gap: 14px;
  padding: 10px 0;
  border-top: 1px solid var(--border);
}

.rk-day-foot {
  padding: 14px 18px 18px;
  border-top: 1px solid var(--border);
  max-width: 66ch;
  text-wrap: pretty;
}


/* --------------------------------------------------------------------------
   Your watches */

.rk-watches { display: grid; gap: 12px; margin-top: 20px; }

.rk-watch {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
  padding: 16px 18px;
}

@media (min-width: 700px) {
  .rk-watch { grid-template-columns: 1fr auto; align-items: center; }
}

.rk-watch-main { display: flex; gap: 14px; align-items: center; min-width: 0; }
.rk-watch-main > i { font-size: var(--i-lg); color: var(--text-secondary); flex: none; }
.rk-watch-what { display: grid; gap: 3px; min-width: 0; }
.rk-watch-side { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

.rk-watch-edit, .rk-watch-confirm {
  grid-column: 1 / -1;
  margin-top: 4px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}

.rk-watch-confirm > p { max-width: 56ch; text-wrap: pretty; }

.rk-watch-edit-row {
  display: flex;
  gap: 9px;
  align-items: center;
  margin-top: 10px;
  flex-wrap: wrap;
}

/* `.rk-modal-box input` joined this list at cutover (chip C12b) and it is the
   same failure C16 found on the consent bar, one layer in. The one-time question
   about the athlete's name was ported from the old shell onto `.rk-modal`, its
   markup was correct, every test was green, and its text box rendered as a 31px
   browser-default field with an inset border inside a finished card, because no
   rule in this file had ever had to style an input inside a modal. Nothing here
   is new: it is the design's own text field, reached by one more selector, which
   is also what keeps the 48px `--tap` target it already promises. */
.rk-watch-edit-row input,
.rk-modal-box input,
.rk-row-edit input {
  flex: 1 1 200px;
  min-width: 0;
  min-height: var(--tap);
  padding: 0 13px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  background: var(--surface-0);
  color: var(--text-primary);
  font-size: var(--t-body);
}

.rk-watch-edit > p, .rk-watch-edit-row + p { margin-top: 8px; max-width: 54ch; }

.rk-pair { padding: 20px; margin-top: 16px; }
.rk-steps { margin-top: 10px; }

.rk-step {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  padding: 13px 0;
  border-top: 1px solid var(--border);
}

.rk-step > div > p + p { margin-top: 3px; max-width: 56ch; }

.rk-step-n {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  flex: none;
  border-radius: 50%;
  background: var(--accent-bg);
  color: var(--accent);
  font-size: var(--t-small);
  font-variant-numeric: tabular-nums;
}

.rk-pair-form { display: flex; gap: 8px; margin-top: 16px; flex-wrap: wrap; }

/* The code box. Wide tracking and the mono face, because the athlete is copying
   eight characters off a watch face and every one of them has to be told apart
   from the next. */
.rk-pair-form input {
  width: 190px;
  min-height: var(--tap);
  padding: 0 12px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  background: var(--surface-0);
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: var(--t-card);
  letter-spacing: .12em;
  text-align: center;
  text-transform: uppercase;
}

.rk-pair > p { margin-top: 12px; max-width: 60ch; text-wrap: pretty; }


/* --------------------------------------------------------------------------
   Account */

.rk-me {
  display: flex;
  gap: 18px;
  align-items: center;
  flex-wrap: wrap;
  padding: 20px;
  margin-top: 22px;
}

.rk-me > div { min-width: 0; }
.rk-me p + p { margin-top: 5px; max-width: 52ch; }
.rk-col > .rk-founder { margin-top: 22px; }
.rk-col > .rk-rows { margin-top: 16px; }

.rk-danger { border-color: var(--alert); }
.rk-danger > .rk-row:first-child { border-top: 0; }

.rk-del > p { max-width: 56ch; text-wrap: pretty; }
.rk-del-facts { margin-top: 14px; }

.rk-del-facts > p {
  display: flex;
  gap: 10px;
  align-items: baseline;
  padding: 8px 0;
  border-top: 1px solid var(--border);
}

.rk-del-facts i { color: var(--alert); font-size: var(--i-sm); flex: none; }


/* --------------------------------------------------------------------------
   The exercise library */

.rk-search { display: flex; gap: 10px; align-items: center; margin-top: 22px; flex-wrap: wrap; }

.rk-search-box {
  position: relative;
  display: flex;
  align-items: center;
  flex: 1 1 220px;
}

.rk-search-box i {
  position: absolute;
  inset-inline-start: 12px;
  font-size: var(--i-md);
  color: var(--text-muted);
  pointer-events: none;
}

.rk-search-box input {
  width: 100%;
  min-height: var(--tap);
  padding: 0 12px 0 38px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  background: var(--surface-2);
  color: var(--text-primary);
  font-size: var(--t-body);
}

.rk-lib-chips { margin-top: 12px; }
.rk-lib-list { margin-top: 18px; overflow: hidden; }

.rk-lib-row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 4px 16px;
  align-items: center;
  width: 100%;
  padding: 16px 18px;
  border: 0;
  border-top: 1px solid var(--border);
  background: transparent;
  color: var(--text-primary);
  text-align: left;
  cursor: pointer;
  transition: background var(--motion);
}

.rk-lib-row:first-child { border-top: 0; }
.rk-lib-row:hover { background: var(--surface-1); }
.rk-lib-row > i { font-size: var(--i-lg); color: var(--text-muted); }
.rk-lib-what { display: grid; gap: 3px; min-width: 0; }

.rk-back { margin-bottom: 10px; display: inline-flex; align-items: center; gap: 7px; }
.rk-back i { font-size: var(--i-md); }

.rk-cue {
  padding: 16px 18px;
  margin-top: 18px;
  border-left: 3px solid var(--accent);
}

.rk-cue > .rk-lede { margin-top: 6px; max-width: 56ch; }
.rk-cue > p + p { margin-top: 6px; }

.rk-lib-illegal { padding: 16px 18px; margin-top: 16px; background: var(--surface-1); }
.rk-lib-illegal > p { margin-top: 6px; max-width: 60ch; text-wrap: pretty; }

.rk-facts { padding: 18px; margin-top: 16px; }

.rk-fact {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 11px 0;
  border-top: 1px solid var(--border);
}

.rk-fact:first-of-type { margin-top: 10px; }
.rk-fact > span:last-child { text-align: end; }

.rk-how { padding: 18px; background: var(--surface-1); }
.rk-how > p { max-width: 66ch; text-wrap: pretty; }
.rk-how > p + p { margin-top: 10px; }

.rk-chip[data-legal] { border-color: var(--accent); }


/* --------------------------------------------------------------------------
   Setup, and the onboarding editors it borrows

   **These controls are js/fields.js's, and that is deliberate.** They are the
   same controls onboarding asks the questions with, writing through the same
   endpoint with the same impact check, so there is one place a validation rule
   lives. What that costs is this block: their class names are the live site's
   (`ob-*`, `seg`, `chip`) and they are styled here, scoped under `.rk-editor`,
   rather than being rewritten under new names.

   It is a compatibility layer with a known end. When the redesign takes
   onboarding, the controls move once and this block goes with them. Until then
   the alternative is two editors for one question, and the one nobody is
   looking at is the one that goes wrong. */

.rk-setup-again { display: inline-flex; align-items: center; gap: 8px; margin-top: 16px; }
.rk-setup-again i { font-size: var(--i-md); color: var(--text-muted); }

.rk-ask { max-width: 56ch; text-wrap: pretty; }
.rk-editor > * + * { margin-top: 12px; }

.rk-editor .seg { display: flex; flex-wrap: wrap; gap: 8px; }

/* `min-width` so a two-letter answer is a pill and not a circle. Every one of
   these is `--tap` tall and `kg` is four characters of padding wide, so the
   units step drew two discs beside a "stones" pill and the row read as three
   different kinds of control. Seen on /onboarding by chip C19; /setup has
   drawn it the same way since C5. */
.rk-editor .seg button,
.rk-editor .chip {
  min-height: var(--tap);
  min-width: 64px;
  padding: 0 16px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--text-secondary);
  font-size: var(--t-small);
  cursor: pointer;
  transition: background var(--motion), color var(--motion), border-color var(--motion);
}

.rk-editor .seg button:hover,
.rk-editor .chip:hover { background: var(--surface-2); }

.rk-editor .seg button.on,
.rk-editor .chip.on {
  border-color: var(--accent);
  background: var(--accent-bg);
  color: var(--text-primary);
}

.rk-editor .ob-checks { display: flex; flex-wrap: wrap; gap: 8px; }
.rk-editor .ob-cards { display: grid; gap: 10px; }

/* A choice card is a NAME AND A BLURB, two children, and this rule read
   `display: block` until chip C19 drove it. js/fields.js writes `<b>Metric</b>`
   followed by `<span>Kilos for everything.</span>`, so a block box ran the two
   together as "MetricKilos for everything." and the whole units step read as
   nonsense. It has been wrong on /setup since C5 and nothing went red: no
   test on this site reads a computed style, and a card that renders is a card
   that renders. Found by opening the page. The column and the quieter span are
   what css/app.css always did, in this file's tokens. */
.rk-editor .ob-choice {
  display: flex;
  flex-direction: column;
  gap: 3px;
  width: 100%;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  background: var(--surface-2);
  color: var(--text-primary);
  text-align: left;
  cursor: pointer;
}

.rk-editor .ob-choice b { font-weight: 500; }
.rk-editor .ob-choice span { font-size: var(--t-small); color: var(--text-secondary); }

.rk-editor .ob-choice.on { border-color: var(--accent); background: var(--accent-bg); }

.rk-editor .ob-row,
.rk-editor .ob-dob,
.rk-editor .ob-anchor-inputs {
  display: flex;
  gap: 9px;
  align-items: center;
  flex-wrap: wrap;
}

.rk-editor input,
.rk-editor select,
.rk-editor .input {
  min-height: var(--tap);
  min-width: 84px;
  padding: 0 12px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  background: var(--surface-0);
  color: var(--text-primary);
  font-size: var(--t-body);
}

.rk-editor .ob-unit,
.rk-editor .ob-val { font-size: var(--t-small); color: var(--text-secondary); }

.rk-editor .ob-hint,
.rk-editor .why-ask,
.rk-editor .seed-note,
.rk-editor .ob-units-detail {
  margin-top: 8px;
  font-size: var(--t-small);
  color: var(--text-secondary);
  max-width: 58ch;
  text-wrap: pretty;
}

/* `.ob-sub` IS THE LABEL ABOVE A CONTROL, and it was grouped with the hints
   above until chip C19 drove the About You step, where four of them stack: Date
   of birth, Sex, Height, Bodyweight. Drawn as secondary with no space of its
   own, each label sat flush under the previous control and read as a caption for
   the thing above it rather than as the name of the thing below. Ink and a gap
   are the whole fix. `.rk-editor > * + *` cannot supply the gap, because these
   are nested inside one editor element rather than being its children. */
.rk-editor .ob-sub {
  margin-top: 20px;
  font-size: var(--t-small);
  color: var(--text-primary);
  max-width: 58ch;
}

.rk-editor .ob-sub:first-child { margin-top: 0; }

.rk-editor .ob-anchors { display: grid; gap: 12px; }
.rk-editor .ob-anchor { padding: 12px 14px; background: var(--surface-1); border-radius: var(--r); }
.rk-editor .ob-anchor-name { font-size: var(--t-small); }

.rk-editor .ob-ack { display: flex; gap: 10px; align-items: flex-start; }
.rk-editor .ob-ack input { min-width: 0; min-height: 0; }

.rk-editor .opt-tag {
  display: inline-block;
  margin-inline-start: 8px;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  background: var(--surface-3);
  color: var(--text-secondary);
  font-size: var(--t-micro);
}

.rk-editor .ob-x {
  min-height: 32px;
  padding: 0 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
}

/* ==========================================================================
   9. THE CONTENT PAGES
   ==========================================================================

   FM-52 chip C10: the exercise pages, the muscle and equipment hubs, the guides
   and their two indexes. Appended below everything C1, C4, C3, C8, C9 and C5
   wrote, for the reason each of those gave in turn: more than one chip is in
   this file at a time, so a rebase should be an append rather than a merge.
   Nothing above this line moved. Chip C11 takes section 10.

   THE PAGES THESE STYLE ARE STATIC HTML, generated by
   `scripts/content_gen.py` and served behind the `next-` gate. Every word is in
   the source before any JavaScript runs, which is the whole point of a content
   page: a crawler runs no scripts. So this section styles markup rather than
   components, and there is nothing here for a module to instantiate.

   IT REUSES THE SHELL RATHER THAN DRAWING A SECOND ONE. `.rk-top`, `.rk-nav`,
   `.rk-mark`, `.rk-who` and `.rk-disc` are C9's public bar and are unchanged
   here; `.rk-eyebrow--public`, `.rk-lede`, `.rk-voice`, `.rk-num` and
   `.rk-btn--primary` are C1's. What this section adds is the article furniture
   the app has never needed: a breadcrumb, a fact block, a numbered how-to, an
   engine run, a card grid and a footer.

   ONE THING IS DELIBERATELY MISSING AND IS HELD FOR DESIGN. The delivered
   prototype opens an exercise page and a guide with a 1600 x 900 photograph, and
   draws a 150px card image on every "read next" card. There are no photographs:
   Garmin's and Hevy's exercise images are copyrighted, `brand/brandgen.py` makes
   marks and icons rather than photography, and an openly licensed set has not
   been chosen. So there is no figure class here at all rather than an empty
   frame on every page, and the pages open on the cue instead. When Design
   settles the imagery it is one block in this section and one field in
   `content/exercises/*.json`; nothing in the generator's structure moves. */


/* --------------------------------------------------------------------------
   The article column

   Content pages are longer than any app screen, so they need vertical rhythm
   the app has never had to express: a heading knows how much room to leave
   above itself rather than every block being spaced by the thing before it. */

.rk-content h2 {
  margin-top: 48px;
  text-wrap: pretty;
}

.rk-content h2 + p,
.rk-content h2 + ol,
.rk-content h2 + ul,
.rk-content h2 + div,
.rk-content h2 + dl {
  margin-top: 14px;
}

.rk-content .rk-head + * { margin-top: 26px; }

/* The honest count under a hub's lede. It belongs to the head rather than to
   what follows it, so it takes its own gap instead of inheriting the block gap
   above. */
.rk-content .rk-head > .rk-lede + p { margin-top: 12px; }

/* The h1 on a content page takes the ramping display step rather than the page
   step. It is the one heading on the site with nothing above it competing for
   attention, and 32px in the middle of a 760px column reads as a section rather
   than as a title. */
.rk-content .rk-head > h1 {
  font-size: var(--t-fig);
  letter-spacing: var(--ls-display);
}

.rk-content .rk-head > .rk-lede { max-width: 62ch; }


/* --------------------------------------------------------------------------
   The breadcrumb

   Drawn as a real <nav> with the separators marked aria-hidden, so a screen
   reader hears the trail as three links rather than as "chest slash". The
   matching BreadcrumbList in the page's structured data is built from the same
   list in the generator, so the two cannot disagree. */

.rk-crumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 9px;
  font-size: var(--t-small);
  color: var(--text-muted);
  margin-bottom: 18px;
}

.rk-crumb > span[aria-hidden] { color: var(--border-strong); }


/* --------------------------------------------------------------------------
   The cue

   The one sentence on an exercise page that also reaches the athlete's wrist
   mid set, so it is set apart in the voice face and attributed. A left edge
   rather than a card, on the same reading C4 settled for the state banner: the
   flat-card rule is about cards, and an edge is what tells a thing that is not
   a card apart at a glance. */

.rk-cuequote {
  margin: 26px 0 0;
  padding: 16px 20px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--record);
  border-radius: var(--r-card);
  background: var(--surface-2);
}

.rk-cuequote > p {
  font-family: var(--font-voice);
  font-style: italic;
  font-size: var(--t-lede);
  text-wrap: pretty;
}

.rk-cuequote > footer { margin-top: 8px; }


/* --------------------------------------------------------------------------
   The fact block

   A definition list, because that is what it is: a field name and its value,
   nine or ten times. One column on a phone and two from 700px, with the label
   column fixed so the values line up down the page.

   It is generated entirely from `reckoner/exercises.json`, so nothing in it is
   a sentence somebody wrote about a movement. */

.rk-spec {
  margin: 24px 0 0;
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  background: var(--surface-1);
  overflow: hidden;
}

.rk-specrow {
  display: grid;
  gap: 4px 16px;
  padding: 13px 18px;
}

.rk-specrow + .rk-specrow { border-top: 1px solid var(--border); }

.rk-specrow > dt {
  font-size: var(--t-micro);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.rk-specrow > dd { margin: 0; }

@media (min-width: 700px) {
  .rk-specrow { grid-template-columns: 200px minmax(0, 1fr); }
  .rk-specrow > dt { padding-top: 2px; }
}


/* --------------------------------------------------------------------------
   The numbered how-to, and what goes wrong

   Both are real lists so the count and the order reach a screen reader without
   the markup having to say so. The ordinal is drawn from a counter rather than
   from a number in the markup, because a step somebody reorders in the JSON
   must not need its number changing too. */

.rk-howto {
  counter-reset: rk-step;
  list-style: none;
  margin: 0;
  padding: 0;
}

.rk-howto > li {
  counter-increment: rk-step;
  display: grid;
  grid-template-columns: 32px minmax(0, 1fr);
  gap: 16px;
  padding: 16px 0;
  border-top: 1px solid var(--border);
}

.rk-howto > li::before {
  content: counter(rk-step);
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--accent-bg);
  color: var(--accent);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-small);
}

.rk-howto > li > h3 { grid-column: 2; }
.rk-howto > li > p {
  grid-column: 2;
  margin-top: 6px;
  color: var(--text-secondary);
  text-wrap: pretty;
}

.rk-misses {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 12px;
}

.rk-miss {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 16px 18px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
}

.rk-miss > i {
  color: var(--alert);
  font-size: var(--i-md);
  flex: none;
  margin-top: 3px;
}

.rk-miss h3 { font-size: var(--t-body); }
.rk-miss p { margin-top: 5px; text-wrap: pretty; }


/* --------------------------------------------------------------------------
   An engine run

   The table is the chip's whole argument, so it is worth saying what it is
   drawing. Each row is one week the real engine actually decided, and the third
   column carries the verdict, the delta and the sentence `reasoning.py` wrote,
   stacked. The sentence is the point rather than a footnote to it: a table of
   numbers is an illustration and a table of numbers each carrying its own
   reason is the product.

   It scrolls sideways inside its own box below 700px rather than reflowing to
   cards. Three columns of short values fit a phone, and a run that turned into
   eighteen stacked blocks would lose the one thing the table is for, which is
   reading down a column and seeing when the load moved. */

.rk-run {
  margin-top: 18px;
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  background: var(--surface-2);
  padding: 4px 18px 16px;
  overflow-x: auto;
}

.rk-run-table {
  width: 100%;
  border-collapse: collapse;
  text-align: left;
}

.rk-run-caption {
  text-align: left;
  padding: 14px 0 4px;
  font-size: var(--t-micro);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted);
}

.rk-run-table th,
.rk-run-table td {
  padding: 12px 14px 12px 0;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
  font-weight: 400;
}

.rk-run-table thead th {
  font-size: var(--t-micro);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-muted);
  white-space: nowrap;
}

.rk-run-table tbody th {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
  white-space: nowrap;
}

.rk-run-table tbody td:first-of-type { white-space: nowrap; }
.rk-run-table tbody tr:last-child th,
.rk-run-table tbody tr:last-child td { border-bottom: 0; }

.rk-run-table b { font-weight: 500; }

.rk-run-why {
  display: block;
  margin-top: 5px;
  max-width: 58ch;
  font-size: var(--t-small);
  color: var(--text-secondary);
  text-wrap: pretty;
}

.rk-run > p { margin-top: 12px; }
.rk-run code { font-family: var(--font-mono); font-size: var(--t-micro); }


/* --------------------------------------------------------------------------
   The questions

   A <details> per question, so the answers are in the source for a crawler and
   collapsed for a reader, with no JavaScript in either direction. The marker is
   replaced rather than hidden, because a disclosure with no affordance is a
   heading people do not press. */

.rk-faqs { display: grid; }

.rk-faq { border-top: 1px solid var(--border); }
.rk-faq:last-child { border-bottom: 1px solid var(--border); }

.rk-faq > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 18px 0;
  min-height: var(--tap);
  cursor: pointer;
  list-style: none;
}

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

.rk-faq > summary::after {
  content: "+";
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--t-lede);
  color: var(--accent);
}

.rk-faq[open] > summary::after { content: "\2212"; }

.rk-faq > summary h3 { text-wrap: pretty; }

.rk-faq > p {
  padding: 0 0 18px;
  max-width: 62ch;
  text-wrap: pretty;
}


/* --------------------------------------------------------------------------
   Pills

   A row of movement names. A link when that movement has a page and a flat
   chip when it does not, which is the whole of how this section reports what
   is written up and what is not: a name that is not a link is a page that does
   not exist yet, and there is never a link to one that is missing. */

.rk-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
}

.rk-pill-link {
  display: inline-flex;
  align-items: center;
  min-height: 40px;
  padding: 0 16px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-pill);
  font-size: var(--t-small);
  color: var(--text-secondary);
  transition: color var(--motion), border-color var(--motion);
}

a.rk-pill-link:hover {
  color: var(--accent);
  border-color: var(--accent);
  text-decoration: none;
}

.rk-pill-link--flat {
  border-style: dashed;
  color: var(--text-muted);
}


/* --------------------------------------------------------------------------
   The hubs and the indexes */

.rk-hubgrid {
  display: grid;
  gap: 14px;
  margin-top: 28px;
}

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

@media (min-width: 1100px) {
  .rk-hubgrid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.rk-hubcard {
  display: block;
  padding: 20px;
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  background: var(--surface-2);
  color: var(--text-primary);
  transition: border-color var(--motion);
}

a.rk-hubcard:hover { border-color: var(--border-strong); text-decoration: none; }

.rk-hubcard h3 { margin-top: 8px; text-wrap: pretty; }
.rk-hubcard .rk-voice { display: block; margin-top: 10px; color: var(--text-secondary); }
.rk-hubcard > p:last-child { margin-top: 12px; }

/* A movement with no page yet. Dimmed rather than hidden, because the hub's job
   is to say what Reckoner can prescribe and hiding two thirds of that would
   make it a worse answer than the library it is drawn from.

   The border is dashed for the same reason the flat pill's is, and it is doing
   real work rather than decorating: a quieter fill alone is not enough to tell
   a reader which cards they can press, and a grid where two thirds of the cards
   silently do nothing on a tap is worse than one that says which. */
.rk-hubcard--flat {
  background: var(--surface-1);
  border-style: dashed;
}

.rk-hubcard--flat h3 { color: var(--text-secondary); }

.rk-hub-more { margin-top: 44px; }

.rk-browsegrid {
  display: grid;
  gap: 10px;
  margin-top: 14px;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

@media (min-width: 700px) {
  .rk-browsegrid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

.rk-browse {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-height: 44px;
  padding: 0 14px;
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--surface-2);
  color: var(--text-primary);
}

.rk-browse:hover { border-color: var(--accent); text-decoration: none; }

.rk-namelist {
  margin: 14px 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 6px 18px;
  color: var(--text-secondary);
  font-size: var(--t-small);
}

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

@media (min-width: 1100px) {
  .rk-namelist { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}


/* --------------------------------------------------------------------------
   The guides index */

.rk-guidelead {
  display: grid;
  gap: 28px;
  margin-top: 28px;
  padding: 28px;
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  background: var(--surface-2);
  color: var(--text-primary);
  transition: border-color var(--motion);
}

.rk-guidelead:hover { border-color: var(--accent); text-decoration: none; }
.rk-guidelead h2 { margin-top: 12px; max-width: 26ch; text-wrap: pretty; }
.rk-guidelead .rk-lede { margin-top: 12px; }

@media (min-width: 900px) {
  .rk-guidelead { grid-template-columns: 1.3fr 1fr; align-items: center; }
}

.rk-guidelead-fig {
  padding: 20px;
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  background: var(--surface-1);
}

.rk-leadrows {
  margin: 12px 0 0;
  padding: 0;
  list-style: none;
}

.rk-leadrows > li {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 9px 0;
  border-top: 1px solid var(--border);
  font-size: var(--t-small);
  color: var(--text-secondary);
}

.rk-leadrows > li > span:first-child { color: var(--text-muted); }
.rk-leadrows > li > span:last-child { color: var(--text-primary); }

.rk-guidelead-fig > p { margin-top: 12px; }

.rk-guidegrid {
  display: grid;
  gap: 14px;
  margin-top: 14px;
}

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

.rk-guidecard {
  display: block;
  padding: 22px;
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  background: var(--surface-2);
  color: var(--text-primary);
  transition: border-color var(--motion);
}

.rk-guidecard:hover { border-color: var(--accent); text-decoration: none; }
.rk-guidecard h3 { margin-top: 10px; text-wrap: pretty; }
.rk-guidecard p { margin-top: 10px; }


/* --------------------------------------------------------------------------
   The article */

.rk-artby {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-top: 22px;
  padding: 16px 0;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}

.rk-artby img { display: block; }

/* Long-form body copy. One step up from the app's body size and looser leading,
   because these are paragraphs somebody reads for four minutes rather than a
   figure somebody reads between sets. It is still a token: `--t-lede` is the
   step the scale already carries for exactly this. */
.rk-prose > p {
  margin-top: 20px;
  font-size: var(--t-lede);
  line-height: 1.7;
  max-width: 62ch;
  text-wrap: pretty;
}

.rk-prose > h2 { margin-top: 44px; }

.rk-pull {
  margin: 32px 0 0;
  padding: 0 0 0 22px;
  border-left: 3px solid var(--accent);
}

.rk-pull > p {
  font-family: var(--font-voice);
  font-style: italic;
  font-size: var(--t-card);
  line-height: 1.45;
  text-wrap: pretty;
}

.rk-takeaways {
  margin-top: 36px;
  padding: 22px;
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  background: var(--surface-1);
}

.rk-takeaways > ul {
  margin: 12px 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 10px;
}

.rk-takeaways li {
  display: flex;
  gap: 11px;
  align-items: flex-start;
  color: var(--text-secondary);
  text-wrap: pretty;
}

.rk-takeaways i {
  color: var(--success);
  font-size: var(--i-sm);
  flex: none;
  margin-top: 5px;
}

.rk-readnext { margin-top: 44px; }


/* --------------------------------------------------------------------------
   The one thing every content page is for

   A reader who got this far is the reader worth asking, so the ask is at the
   bottom of every page and nowhere else on it. No interstitial, no floating
   bar, and no second copy halfway down: a page that interrupts the answer it
   promised is a page nobody links to. */

.rk-cta {
  margin-top: 44px;
  padding: 26px;
  border: 1px solid var(--accent);
  border-radius: var(--r-card);
  background: var(--surface-2);
}

.rk-cta-line {
  font-size: var(--t-card);
  line-height: var(--lh-head);
  letter-spacing: var(--ls-head);
  max-width: 44ch;
  text-wrap: pretty;
}

.rk-cta > p + p { margin-top: 10px; max-width: 56ch; }
.rk-cta > a { margin-top: 18px; display: inline-flex; }


/* --------------------------------------------------------------------------
   The footer

   The signed-in screens have no footer at all: an app screen ends at its last
   card. A content page needs one, because a reader arriving from a search has
   no nav in their head and the policy links have to be reachable from the
   bottom of the page they landed on.

   IT IS `rk-sitefoot` BECAUSE `rk-foot` WAS ALREADY TAKEN, and that is worth a
   sentence rather than a rename in silence. Section 8 declares
   `.rk-foot { max-width: 64ch }` for the footnote paragraph four screen modules
   put under a card. This section shipped with the obvious name first and the
   site footer came out 645px wide inside a 1265px window, looking like a
   deliberate narrow column rather than like a collision, with every test green:
   both rules were valid, both applied, and the second one silently lost. That
   is the failure mode a numbered-sections file has, so `content_test.py` now
   refuses any class this section declares that an earlier section already
   declared.

   At cutover this is also where the consent banner's "change this" line lands,
   which is why the row is a flex that can take a second block on the right. */

.rk-sitefoot {
  border-top: 1px solid var(--border);
  background: var(--surface-1);
}

.rk-sitefoot > .rk-col {
  display: flex;
  flex-wrap: wrap;
  gap: 18px 28px;
  align-items: baseline;
  justify-content: space-between;
  padding-top: 32px;
  padding-bottom: 40px;
}

.rk-sitefoot-mark {
  font-size: var(--t-small);
  color: var(--text-muted);
}

.rk-sitefoot-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
}

.rk-sitefoot-nav a {
  font-size: var(--t-small);
  color: var(--text-secondary);
}

.rk-sitefoot-nav a:hover { color: var(--accent); }


/* ==========================================================================
   10. WHAT THIS GYM IS STOCKED WITH
   ==========================================================================

   FM-52 chip C11, bug B41 part two. Appended below everything above it for the
   reason section 8 gives: several chips are open in this file at once, so a
   rebase should be an append rather than a merge, and nothing above this line
   moved.

   IT IS SECTION 10 BECAUSE C10 WAS OPEN AT THE SAME TIME and took 9. That is
   the collision section 8 predicts happening a second time, resolved the same
   way: both sections stay, in number order, and neither is folded into the
   other.

   There is very little here, and that is the design rather than an omission.
   The six answers are ordinary settings rows, so they are `.rk-rows` rows and
   inherit everything section 8 already decided about a row, a value, an open
   editor and the control inside it. What this section adds is the two things
   that are true of this card and of no other: it holds corrections to a default
   rather than answers to questions, and every row in it ends a training block.

   NO NEW COLOUR AND NO NEW TYPE STEP. A seventh surface or a ninth type step is
   a decision made once, in section 1, for every screen. This borrows
   `--surface-2` and `--warning`, both of which section 1 has already cleared
   against every ground they sit on. */

/* The card reads a shade quieter than the settings blocks above it, because
   nothing in it is waiting on the athlete. Somebody scrolling their setup
   should be able to tell at a glance that this is the one part they can pass
   over, which is the opposite of what the gap badge does two cards up. */
.rk-gym { background: var(--surface-2); }

/* An unset row says "Standard", which is a value rather than a gap, so it takes
   the ordinary value colour and never the badge. `js/fields.js` decides the
   word; this only makes sure a card full of them reads as settled. */
.rk-gym .rk-said-value { color: var(--text-secondary); }
.rk-gym .rk-row[data-open] { background: var(--surface-1); }

/* The standing warning under the rows, and the empty card's one line. Both sit
   inside the card's padding rather than under it, so the sentence belongs to
   the six answers above it and not to whatever comes next. */
.rk-gym > p {
  padding: 14px 18px 16px;
  border-top: 1px solid var(--border);
  max-width: 62ch;
  text-wrap: pretty;
}

#rk-gym-warns { color: var(--warning); }


/* ==========================================================================
   11. THE FOOTER UNDER EVERY SCREEN, AND THE COUNT IN THE MENU
   ==========================================================================

   Bugs B47 and B46. Appended below everything above it for the reason sections
   8 and 10 both give: more than one chip is open in this file at a time, so a
   rebase should be an append rather than a merge, and nothing above this line
   moved.

   IT IS `rk-appfoot` BECAUSE BOTH OBVIOUS NAMES ARE TAKEN, and the history is
   why that sentence is here rather than a rename in silence. Section 8 owns
   `.rk-foot`, the footnote line four screen modules draw under a card. Section 9
   owns `.rk-sitefoot`, the content pages' one row strip, which is already named
   that because it collided with the first one and shipped 645px wide inside a
   1265px window with every test green. This is the third footer in the file and
   the first two are the reason it is checked rather than remembered:
   `content_test.py` refuses any class a section heads that an earlier one
   already owns, across every section, so a fourth author gets told.

   IT IS A DIFFERENT COMPONENT FROM `.rk-sitefoot` RATHER THAN A WIDER ONE. That
   one is a mark and five links for a reader who arrived from a search and has no
   nav in their head. This is a four column menu, and it is the only route in the
   product to the exercise pages, the board, the guides and the founding-member
   badge. Sharing one class would mean one rule serving a strip and a menu, which
   is how the first collision happened.

   ONE, TWO, THEN FOUR, AND WRITTEN OUT RATHER THAN INFERRED. The first attempt
   was `repeat(auto-fit, minmax(170px, 1fr))`, which needs no breakpoint at all
   and was measured on the live page giving three columns and an orphan on the
   fourth row at 753px. Every floor big enough to stop that moves the orphan to
   some other width rather than removing it, because auto-fit counts what fits
   and four is not what fits at most tablet widths. So the ladder is two
   min-width queries and the count never lands on three. Narrow first, min-width
   only, which is section 2's rule. */

.rk-appfoot {
  margin-top: 56px;
  border-top: 1px solid var(--border);
  background: var(--surface-1);
}

/* Before the module has filled it. The host ships in the markup so the page is
   honest about carrying a footer, and an unfilled one must not draw a hairline
   across the bottom of a screen that is still loading. */
.rk-appfoot:empty { border-top: 0; }

.rk-appfoot > .rk-col {
  padding-top: 40px;
  padding-bottom: 40px;
}

.rk-appfoot-cols {
  display: grid;
  grid-template-columns: 1fr;
  gap: 28px 32px;
}

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

@media (min-width: 900px) {
  .rk-appfoot-cols { grid-template-columns: repeat(4, 1fr); }
}

.rk-appfoot-col {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
}

/* A column head, not a page heading. It is an `h2` because the column is a
   labelled nav and a screen reader listing the landmarks should get the four
   names; it is drawn at the smallest step in the muted colour because on the
   screen it is a label. */
.rk-appfoot-head {
  font-size: var(--t-micro);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted);
}

.rk-appfoot-col a {
  font-size: var(--t-small);
  color: var(--text-secondary);
  min-height: 28px;
  display: inline-flex;
  align-items: center;
}

.rk-appfoot-col a:hover { color: var(--accent); }

/* The bottom bar: the mark and the line on the left, the policies and the
   cookie sentence after them. It wraps rather than scrolls, because the cookie
   sentence is the widest thing on it and a policy link an athlete has to scroll
   sideways to reach is a policy link they do not reach. */
.rk-appfoot-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 28px;
  margin-top: 36px;
  padding-top: 24px;
  border-top: 1px solid var(--border);
}

.rk-appfoot-mark {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}

.rk-appfoot-line {
  font-family: var(--font-voice);
  font-size: var(--t-small);
  color: var(--text-secondary);
}

.rk-appfoot-legal {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
}

.rk-appfoot-legal a {
  font-size: var(--t-small);
  color: var(--text-secondary);
}

.rk-appfoot-legal a:hover { color: var(--accent); }

/* The one sentence on this site that a change somewhere else can turn into a
   false statement, which is what `website/cookies.html` and `cookies_test.py`
   exist for. It names the cookie and sends the rest to the policy rather than
   counting them: see the note above footer() in js/screen-chrome.js. */
.rk-appfoot-cookie {
  font-size: var(--t-micro);
  color: var(--text-muted);
  margin-inline-start: auto;
}

.rk-appfoot-cookie code {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
}

/* The count on the menu's Setup row, restored with B46. It belongs beside
   section 8's `.rk-menu-*` block and is here instead for the append rule at the
   top of this section. It reads as a thing to finish rather than as a
   notification, so it takes the accent tint rather than the alert colour: an
   unanswered optional question is not a problem, it is work left. */
.rk-menu-count {
  margin-inline-start: auto;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--accent-bg);
  color: var(--accent);
  font-size: var(--t-micro);
  white-space: nowrap;
}

.rk-menu-count:empty { display: none; }


/* ==========================================================================
   12. EDITING THE WEEK
   ==========================================================================

   FM-52 chip C13, bug B50 and the swap half of B53. Appended below everything
   above it for the reason sections 8, 10 and 11 each give in turn: more than one
   chip is open in this file at a time, so a rebase should be an append rather
   than a merge, and nothing above this line moved.

   SECTION 11 WAS THE HIGHEST WHEN THIS WAS WRITTEN, so this is 12. If it
   collides with another chip's 12, the resolution is the one section 8 and
   section 10 both took: both sections stay, in number order, and neither is
   folded into the other.

   EVERY CLASS HERE IS NEW, and that was checked rather than assumed.
   `content_test.py` refuses any class a section heads that an earlier one
   already owns, across the whole file, because `.rk-foot` was declared twice by
   two chips, both rules were valid, the later one lost the cascade on
   `max-width`, and a footer rendered 645px wide inside a 1265px window with
   every test green. The near miss here was `.rk-editor`, which section 8 already
   owns for Setup's field editor: this one is `.rk-ed`, and the two are different
   components rather than one component drawn twice.

   NO NEW COLOUR, NO NEW TYPE STEP AND NO NEW SURFACE. An editor is a set of
   controls, and section 1 has already cleared every colour a control can take
   against every ground it can sit on. What this section adds is arrangement.

   THE ONE MEASUREMENT THAT IS A REQUIREMENT is the number cells. They are
   `--tap` tall, the same 48px section 5 gives Log's set inputs, and for the same
   reason: this is the screen an athlete rebuilds a day on with a phone in one
   hand, and a 32px box is a box you miss. */


/* --------------------------------------------------------------------------
   The tools on a card head, and the icon buttons in them */

.rk-tools {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

/* A square target rather than a glyph with padding round it, so the hit area is
   the same whichever icon is in it and a trash can is no harder to press than a
   pencil. 40px rather than --tap: these sit in a card head beside a pill and are
   not mid-session controls, which is the same call section 4 makes for
   `.rk-action`. */
.rk-iconbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: transparent;
  color: var(--text-secondary);
  font-size: var(--t-body);
  cursor: pointer;
}

.rk-iconbtn:hover { background: var(--surface-1); color: var(--text-primary); }
.rk-iconbtn:disabled { opacity: .5; cursor: default; }

/* Delete is the one irreversible control on this screen, so it says so before
   it is pressed rather than only in the bar that follows. */
.rk-iconbtn--danger { color: var(--alert); }
.rk-iconbtn--danger:hover { background: var(--alert-bg); color: var(--alert); }


/* --------------------------------------------------------------------------
   Deleting a day, and the FM-40 swap offer

   Both are bars that open INSIDE the card they belong to rather than as a
   dialog, because both are about one day and a dialog would cover the day while
   asking about it. They share a shape for that reason: a sentence, then the
   answers as chips. */

.rk-delbar,
.rk-swap {
  padding: 14px 18px;
  border-top: 1px solid var(--border);
  background: var(--surface-1);
}

.rk-delbar > p,
.rk-swap > p { max-width: 58ch; text-wrap: pretty; }
.rk-delbar > .rk-chips,
.rk-swap > .rk-chips { margin-top: 12px; }

/* The offer itself sits on the lift row rather than in the card head, because it
   is about that one lift and a button in the head would be about the day. Full
   width at a phone size and inline above it, which is the same narrow-first
   ladder section 2 sets for everything else. */
.rk-swap-ask {
  margin-top: 10px;
  width: 100%;
}

@media (min-width: 560px) {
  .rk-swap-ask { width: auto; }
}

.rk-swap-line {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
}

.rk-swap-why { margin-top: 6px; }


/* --------------------------------------------------------------------------
   The hotel room's kit

   The chips sit above the window buttons in the same picker, because they are
   the same answer: this is the trip. Turning hotel mode on used to send no kit
   at all, which was right for a second stay in the same hotel and silently wrong
   for every other one. */

.rk-kit > .rk-chips { margin-top: 8px; }

/* What the kit costs, live off /plan/hotel/preview. It sits under the chips
   rather than under the window buttons so it reads as the answer to the chips,
   which is what it is. */
.rk-kit-count {
  margin-top: 10px;
  max-width: 46ch;
  text-wrap: pretty;
}


/* --------------------------------------------------------------------------
   The day editor

   It stands in the place of the day it is editing rather than opening at the
   foot of the page, so Monday becomes the editor and the days under it stay
   where they were. That is why it takes the card's own outline and not a modal:
   an athlete's eye should not have to find it again. */

.rk-ed { padding: 18px; }
.rk-ed > * + * { margin-top: 14px; }

.rk-ed-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}

/* The mode toggle inherits section 8's segmented control, so Training and Rest
   read as the same kind of choice as every other pair on the site. It only needs
   its top margin taking off here, because in the head it is beside the eyebrow
   rather than under a question. */
.rk-ed-head > .rk-seg { margin-top: 0; }

/* The weekday and the name. One column on a phone, because a select and a text
   field side by side at 375px gives the name about eleven characters. */
.rk-ed-id {
  display: grid;
  grid-template-columns: 1fr;
  gap: 10px;
}

@media (min-width: 560px) {
  .rk-ed-id { grid-template-columns: auto 1fr; }
}

.rk-ed-dow,
.rk-ed-name,
.rk-ed-find {
  min-height: var(--tap);
  padding: 0 12px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  background: var(--surface-2);
  color: var(--text-primary);
  font: inherit;
  font-size: var(--t-body);
  width: 100%;
}

.rk-ed-dow:focus-visible,
.rk-ed-name:focus-visible,
.rk-ed-find:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.rk-ed-lifts > * + * { margin-top: 10px; }

/* One lift: what it is, its five numbers, and the control that takes it off.
   The name goes above the numbers at every width rather than beside them, because
   an exercise name is up to about forty characters and five number cells are not
   negotiable: side by side, one of the two has to lose, and it would be the
   numbers. */
.rk-ed-lift {
  padding: 12px 14px;
  background: var(--surface-1);
  border-radius: var(--r);
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 10px 12px;
  align-items: start;
}

.rk-ed-lift-id { min-width: 0; }
.rk-ed-lift-id > p { font-size: var(--t-body); }

/* Placed rather than left to auto-placement, and the reason is worth the line:
   the numbers span both columns, so in document order the remove control lands
   on a THIRD row under them, hanging off the left of the card looking like a
   control for whatever comes next. Reordering the markup instead would put it
   between the name and the numbers in the tab order, which is the wrong answer
   to a layout question. */
.rk-ed-lift > .rk-iconbtn { grid-column: 2; grid-row: 1; }

/* Two across a phone and five across everything else, written out rather than
   left to `auto-fit`. Section 11 has the measurement that settles this: auto-fit
   counts what fits, so the column count lands wherever the viewport happens to
   put it and a five item row breaks to three and an orphan somewhere in the
   middle of the range. Five numbers are one thought and they belong on one line
   the moment there is room for them. */
.rk-ed-nums {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

@media (min-width: 560px) {
  .rk-ed-nums { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}

/* The label above the box rather than a placeholder inside it, so the word is
   still there once a number has been typed. That is the whole failure of a
   placeholder as a label and it is invisible until somebody comes back to a day
   they built last week. */
.rk-ed-cell {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* `.rk-num` is typography and nothing else: it sets the mono face and tabular
   figures and draws no box at all, so an input carrying it alone renders as the
   browser's own control. The box is here, and it is the same box the name field
   above takes, because they are the same kind of thing.

   --tap and 56px are the measurements section 5 already fixed for Log's set
   inputs, and they are requirements for the same reason: this is a screen an
   athlete rebuilds a day on with a phone in one hand. */
.rk-ed-cell input {
  min-height: var(--tap);
  min-width: 56px;
  width: 100%;
  padding: 0 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  background: var(--surface-2);
  color: var(--text-primary);
  font-size: var(--t-body);
}

.rk-ed-cell input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.rk-ed-rest { max-width: 58ch; text-wrap: pretty; }


/* The picker. The list sits directly under the field in the flow rather than
   floating over the lifts, because there is nothing below it worth covering and
   an absolutely positioned dropdown inside a card that scrolls is a dropdown
   that leaves the card. */
.rk-ed-search { position: relative; }

.rk-ed-matches:empty { display: none; }

.rk-ed-matches {
  margin-top: 8px;
  border: 1px solid var(--border);
  border-radius: var(--r);
  overflow: hidden;
}

.rk-ed-match {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  width: 100%;
  min-height: var(--tap);
  padding: 8px 12px;
  border: 0;
  border-top: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text-primary);
  font: inherit;
  font-size: var(--t-body);
  text-align: start;
  cursor: pointer;
}

.rk-ed-match:first-child { border-top: 0; }
.rk-ed-match:hover,
.rk-ed-match[aria-selected="true"] { background: var(--accent-bg); }

.rk-ed-foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}

.rk-ed-foot > p { max-width: 44ch; text-wrap: pretty; }


/* --------------------------------------------------------------------------
   Adding a day

   Under the last card rather than at the top, because it is the thing you do
   after reading the week and not before. */

.rk-addday {
  padding: 16px 18px;
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-card);
}

.rk-addday > p { margin-top: 10px; max-width: 58ch; text-wrap: pretty; }


/* ==========================================================================
   13. CORRECTING A LOGGED SESSION
   ==========================================================================

   FM-52 chip C14, and bug B51. Appended below everything above it for the
   reason sections 8, 10, 11 and 12 each give in turn: more than one chip is open
   in this file at a time, so a rebase should be an append rather than a merge,
   and nothing above this line moved.

   SECTION 12 WAS THE HIGHEST WHEN THIS WAS WRITTEN, so this is 13. A collision
   with another chip's 13 resolves the way 8, 10 and 12 each resolved theirs:
   both sections stay, in number order, and neither is folded into the other.

   EVERY CLASS HERE IS NEW, and that was checked rather than assumed.
   `content_test.py` refuses any class a section heads that an earlier one
   already owns, across the whole file, because `.rk-foot` was declared twice by
   two chips, both rules were valid, the later one lost the cascade on
   `max-width`, and a footer rendered 645px wide inside a 1265px window with
   every test green. The near miss here was `.rk-sess-row`, which section 8 owns
   for a logged lift as it READS: the editable version of the same lift is
   `.rk-fix`, and they are two components rather than one drawn twice.

   WHAT THIS SECTION DOES NOT ADD is most of what the screen draws. The two icon
   buttons on the card head, the delete bar and its chips are all section 12's,
   built for the week's day editor and reused here unchanged, because deleting a
   day and deleting a session are the same question asked about two things. The
   RPE tap is section 5's chip row and the done switch is section 8's segmented
   control. No new colour, no new type step and no new surface: an editor is a
   set of controls, and section 1 has already cleared every colour a control can
   take against every ground it can sit on.

   THE ONE MEASUREMENT THAT IS A REQUIREMENT is the boxes. They are `--tap` tall
   and 56px wide, the same numbers section 5 fixed for Log's set inputs and
   section 12 repeated for the week's, and for the same reason both times: this
   is a correction typed with a phone in one hand, often in the gym the session
   was logged in. */


/* --------------------------------------------------------------------------
   One lift, editable

   It stands in the place of the row it corrects rather than opening a dialog
   over the card, so the posting stays on screen while it is being fixed. That
   is the same call section 12 makes for the day editor, and it matters more
   here: the thing being corrected is the thing being read. */

.rk-fix {
  padding: 14px 22px;
  border-top: 1px solid var(--border);
  background: var(--surface-1);
}

/* A skipped lift is greyed at the name rather than across the whole block, which
   is where `.rk-sess-row` puts it. Dimming an open editor dims the controls the
   athlete is reaching for, and one of them is the switch that undoes the skip. */
.rk-fix[data-skipped] .rk-fix-head > span { color: var(--text-muted); }

.rk-fix-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

/* Both segmented controls on this block lose the top margin the component
   carries everywhere else: the done switch sits beside the name rather than
   under a question, and the RPE sits under its own label in the grid, which is
   already providing the gap. */
.rk-fix-head > .rk-seg,
.rk-fix-cell > .rk-seg { margin-top: 0; }

/* One column on a phone, and the three controls in a row the moment there is
   room. Written out rather than left to `auto-fit` for the reason section 12
   gives: auto-fit counts what fits, so the count lands wherever the viewport
   happens to put it, and reps, load and effort are one thought about one set. */
.rk-fix-grid {
  margin-top: 12px;
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}

@media (min-width: 720px) {
  .rk-fix-grid {
    grid-template-columns: minmax(0, 1fr) auto auto;
    align-items: start;
  }
}

/* The label above the box rather than a placeholder inside it, so the word is
   still there once a number has been typed. */
.rk-fix-cell {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

/* One box per set, wrapping, because a session can carry five sets and a phone
   is 375px wide. */
.rk-fix-reps {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

/* `.rk-num` is typography and nothing else: it sets the mono face and tabular
   figures and draws no box at all, so an input carrying it alone renders as the
   browser's own control. The box is here.

   THE WIDTH IS WRITTEN DOWN, and that is the one thing in this block that is not
   obvious. An `<input>` with no width takes its intrinsic size, which is about
   twenty characters, so a box holding the number 8 measured 220px and two of
   them would not sit side by side in a 406px column. Seen on screen at 1280
   before it was changed: five sets drew as five stacked full width boxes, which
   reads as five different questions rather than one set of five. */
.rk-fix-reps input,
.rk-fix-cell > input {
  min-height: var(--tap);
  min-width: 56px;
  padding: 0 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  background: var(--surface-2);
  color: var(--text-primary);
  font-size: var(--t-body);
}

/* A rep count is one or two digits, so the box is sized for the number it holds
   and a session of five sets reads as one row. */
.rk-fix-reps input { width: 72px; flex: 0 0 auto; }

/* A load is up to five characters (137.5), and it keeps the same box height as
   the reps beside it. */
.rk-fix-cell > input { width: 108px; }

.rk-fix-reps input:focus-visible,
.rk-fix-cell > input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }


/* --------------------------------------------------------------------------
   Saving it, and the bar that asks before a delete */

.rk-fix-foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 14px 22px;
  border-top: 1px solid var(--border);
}

.rk-fix-foot > p { max-width: 46ch; text-wrap: pretty; }

/* Full width under both, because an error about the save belongs under the save
   and not beside it. */
.rk-fix-foot > .rk-field-error { flex: 1 0 100%; }

/* Section 12's delete bar, taking this card's wider gutter so it lines up with
   the rows above it rather than with the day cards it was built for. */
.rk-fix-bar { padding-inline: 22px; }


/* ==========================================================================
   14. THE RECONNECTIONS
   ==========================================================================

   FM-52 chip C18, and bugs B58 and B56. Appended below everything above it for
   the reason sections 8, 10, 11, 12 and 13 each give in turn: more than one chip
   is open in this file at a time, so a rebase should be an append rather than a
   merge, and nothing above this line moved.

   SECTION 13 WAS THE HIGHEST WHEN THIS WAS WRITTEN, so this is 14. A collision
   with another chip's 14 resolves the way 8, 10, 12 and 13 each resolved theirs:
   both sections stay, in number order, and neither is folded into the other.

   EVERY CLASS HERE IS NEW, and that was checked rather than assumed.
   `content_test.py` refuses any class a section heads that an earlier one
   already owns, across the whole file, because `.rk-foot` was declared twice by
   two chips and a footer rendered 645px wide inside a 1265px window with every
   test green. The near miss here was `.rk-search`, which section 9 owns for the
   content build's own box: the library's suggestion form is `.rk-lib-suggest`
   and sits inside the library's own namespace with the rest of that screen.

   WHAT THE NAV RULE BELOW IS FOR. B56 asked for a bottom tab bar and Design
   declined it: a tab bar holds five, and the product has six screens in the bar
   with four more behind the account chip. What was actually wrong was that the
   scrolling row did not say it scrolled. So the row stays, the current screen is
   scrolled to the middle of it by js/screen-chrome.js, and the two fades below
   are what make a cut-off entry read as more rather than as the end.

   THE FADES ARE THE FOUR-LAYER BACKGROUND TRICK, and the reason it is that
   rather than a `mask-image` is that a mask is always on. A fade that stays
   drawn when the row is scrolled fully right says there is more when there is
   not, which is the same lie in the other direction. Two of the four layers are
   `local` covers that move WITH the content and sit over the two `scroll`
   fades, so each fade is hidden exactly when its end is reached, with no
   scroll listener and nothing to keep in step.

   AND THAT IS ALSO WHY THERE IS NO MEDIA QUERY HERE. The obvious version wraps
   this in `max-width: 699px`, because above 700px section 8 lets the row wrap
   instead and a fade on a wrapped row would be a gradient down the side of the
   second line. Two things say no: this file's breakpoints are `min-width` only
   and `foundations_test.py` fails the build on a `max-width` query, and the
   trick already handles it. A row that cannot scroll has `local` and `scroll`
   resolving to the same origin, so each cover sits exactly on its own fade and
   hides it. The covers are wider and hold solid for longer than the fades they
   cover, which is what makes "hides it" mean invisible rather than nearly. */

.rk-nav {
  background:
    linear-gradient(to right, var(--surface-2) 60%, rgba(0, 0, 0, 0)) left / 24px 100% no-repeat local,
    linear-gradient(to left, var(--surface-2) 60%, rgba(0, 0, 0, 0)) right / 24px 100% no-repeat local,
    linear-gradient(to right, var(--topbar-edge), rgba(0, 0, 0, 0)) left / 16px 100% no-repeat scroll,
    linear-gradient(to left, var(--topbar-edge), rgba(0, 0, 0, 0)) right / 16px 100% no-repeat scroll;
  /* The bar's own ground, so the two covers above have something to cover with.
     `--surface-2` is what `.rk-top` is painted in, so this changes nothing about
     how the bar looks at any width. */
  background-color: var(--surface-2);
  /* A scrollbar under the tabs on a phone is chrome nobody asked for, and the
     fades are the affordance now. Firefox takes the standard property and
     WebKit needs its own, so both are written. */
  scrollbar-width: none;
  /* Nothing lands on half a tab. `proximity` rather than `mandatory`, so a
     deliberate small drag is still allowed to stop where it was put. */
  scroll-snap-type: x proximity;
}

.rk-nav::-webkit-scrollbar { display: none; }
.rk-nav a { scroll-snap-align: center; }

/* The link off a movement to its written-up page (bug B58). A card rather than a
   line of text, because it is the one thing on that screen that leaves it, and
   the arrow is the only affordance a card gets in this system. */
.rk-lib-guide {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-top: 18px;
  padding: 16px 18px;
  color: var(--text-primary);
}

.rk-lib-guide:hover { background: var(--surface-1); text-decoration: none; }
.rk-lib-guide > i { font-size: var(--i-lg); color: var(--text-muted); flex: none; }

.rk-lib-guide-what { display: grid; gap: 4px; min-width: 0; }
.rk-lib-guide-what > span:last-child { max-width: 52ch; text-wrap: pretty; }

/* Suggesting a movement the library does not carry (FM-15, rebuilt onto this
   screen by C18). It sits under the list rather than beside the search box: it
   is what an athlete reaches for after the list has failed them, not before. */
.rk-lib-suggest { margin-top: 22px; padding: 18px; }

.rk-lib-suggest-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}

.rk-lib-suggest-head p { margin-top: 6px; max-width: 54ch; text-wrap: pretty; }

.rk-lib-suggest-form {
  display: grid;
  gap: 12px;
  margin-top: 18px;
  padding-top: 18px;
  border-top: 1px solid var(--border);
}

.rk-lib-suggest-form input,
.rk-lib-suggest-form textarea {
  width: 100%;
  padding: 12px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  background: var(--surface-0);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: var(--t-body);
}

.rk-lib-suggest-form input { min-height: var(--tap); }
.rk-lib-suggest-form textarea { min-height: 88px; resize: vertical; }

.rk-lib-suggest-foot {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}

/* The line the server answered with, which is as often good news as not: the
   movement is already there under a spelling nobody searched for. */
.rk-lib-suggest-foot > p { flex: 1 1 24ch; max-width: 48ch; text-wrap: pretty; }


/* ==========================================================================
   15. TODAY'S REMAINDER, AND HISTORY'S TWO PANELS
   ==========================================================================

   FM-52 chip C17, and bugs B48, B53 and B51. One section covering both screens,
   because the two changes share the effort ramp and there is no honest place to
   split a five-colour scale across two numbered sections.

   Appended below everything above it for the reason sections 8, 10, 11, 12, 13
   and 14 each give in turn: more than one chip is open in this file at a time,
   so a rebase should be an append rather than a merge, and nothing above this
   line moved.

   SECTION 14 WAS THE HIGHEST WHEN THIS WAS WRITTEN, so this is 15. A collision
   with another chip's 15 resolves the way every one before it did: both
   sections stay, in number order, and neither is folded into the other.

   EVERY CLASS HERE IS NEW, and that was checked rather than assumed.
   `content_test.py` refuses any class a section heads that an earlier one
   already owns, across the whole file, because `.rk-foot` was declared twice by
   two chips and a footer rendered 645px wide inside a 1265px window with every
   test green. The near misses here were `.rk-pill`, which section 5 owns for
   the watch pill and this section only adds a data-state to, and `.rk-chart-*`,
   which section 8 owns and this section extends with two NEW names rather than
   redeclaring: `.rk-chart-line2` and the dot's `data-rpe`.

   THE EFFORT RAMP FLIPS WITH THE THEME AND THE TIER BANDS DO NOT, which looks
   inconsistent and is the opposite. A band is a BACKGROUND with nothing drawn
   on it, so one value reads in both themes and a ramp that inverted would put
   Elite at the pale end for half the readers. An RPE dot is a FOREGROUND, drawn
   on a card, so it has to clear that card in whichever theme the card is in.
   Both sets are the delivered design's own values, teal at 6 to crimson at 10,
   and never oxide: oxide is the record colour and an RPE 9 that looked like a
   personal best would be the chart congratulating somebody for grinding. */

:root {
  --rpe-6: #2e6a72;
  --rpe-7: #4f7f4a;
  --rpe-8: #8a6d12;
  --rpe-9: #a3591b;
  --rpe-10: #a01f3c;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --rpe-6: #7cc9cf;
    --rpe-7: #8fc884;
    --rpe-8: #d8b13f;
    --rpe-9: #ef9f5a;
    --rpe-10: #f08098;
  }
}

[data-theme="dark"] {
  --rpe-6: #7cc9cf;
  --rpe-7: #8fc884;
  --rpe-8: #d8b13f;
  --rpe-9: #ef9f5a;
  --rpe-10: #f08098;
}


/* --------------------------------------------------------------------------
   Today: the best of the week, and the outcome groups */

/* The two pills on the session head, which can now be two. */
.rk-session-pills {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: flex-end;
}

/* What the engine already knows was your best this week. NOT a streak: see the
   note above bestPill() in js/screen-today.js. It takes the record colour,
   which is the one place oxide is the right answer, and it is allowed to run
   long because it names a movement. */
.rk-best {
  color: var(--record);
  border-color: var(--record);
  white-space: normal;
  max-width: 34ch;
}

.rk-best i { font-size: var(--i-sm); }

/* The note beside an outcome heading. Fixed English rather than a sum, for the
   reason written above BUCKETS in js/screen-today.js. */
.rk-feed-note { color: var(--text-secondary); }

/* The outcome a group carries, as a hairline down its leading edge. THIS IS
   THE ONE COMPONENT THE 3PX LEFT EDGE IS LEGAL ON besides the state banner:
   Design's amendment of 2026-08-01 permits it on the state banner component
   only, never on a card, tile, list row, well or table row. A feed group is
   none of those. It is the same component as the state banner wearing a
   different verdict, which is exactly why the banner's own rule reads the way
   it does, so the boundary is honoured rather than widened.

   The colour is the verdict's own, so a group and the verdicts inside it can
   never disagree: js/screen-today.js buckets on the same TONE table that
   colours each item. */
.rk-feed-group { border-left: 3px solid var(--border-strong); }
.rk-feed-group[data-tone="gain"] { border-left-color: var(--accent); }
.rk-feed-group[data-tone="flat"] { border-left-color: var(--border-strong); }
.rk-feed-group[data-tone="relief"] { border-left-color: var(--success); }
.rk-feed-group[data-tone="ask"] { border-left-color: var(--warning); }

/* Which session a decision came out of. It was the group heading before this
   chip and it is still worth saying, so it sits beside the verdict. */
.rk-feed-day { margin-inline-start: auto; }


/* --------------------------------------------------------------------------
   History: the Lifts controls, and the second series on the body chart */

.rk-lifts-controls {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 18px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}

/* The effort key. The numeral stays in ink and the swatch carries the ramp, so
   contrast never depends on the colour: Design's own chart rule, and the reason
   the numbers sit beside the dots rather than inside them. */
.rk-rpe-key {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.rk-rpe-step { display: inline-flex; align-items: center; gap: 5px; }

.rk-rpe-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--border-strong);
}

.rk-rpe-say { flex: 1 1 22ch; max-width: 46ch; text-wrap: pretty; }

/* One ramp, two places it is drawn: the key above and the dots on every lane
   chart. Written once against `data-rpe` so the two cannot drift. A point with
   no effort recorded carries no attribute at all and keeps the plain accent
   dot, because an absent RPE is not an easy one. */
[data-rpe="6"] { background: var(--rpe-6); }
[data-rpe="7"] { background: var(--rpe-7); }
[data-rpe="8"] { background: var(--rpe-8); }
[data-rpe="9"] { background: var(--rpe-9); }
[data-rpe="10"] { background: var(--rpe-10); }

/* The second series on the body chart. 1px in --border-strong for the thing
   being compared against, where the series being read is 1.5px in --accent:
   the design's one line-weight rule, and what keeps a two-line chart from
   reading as two equal claims. */
.rk-chart-line2 {
  stroke: var(--border-strong);
  stroke-width: 1;
  stroke-dasharray: 4 3;
}

.rk-series-key {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 14px;
}

.rk-series-key > span { display: inline-flex; align-items: center; gap: 8px; }

.rk-series-mark {
  width: 18px;
  border-top: 2px solid var(--accent);
}

.rk-series-mark[data-series="fat"] {
  border-top-width: 1px;
  border-top-style: dashed;
  border-top-color: var(--border-strong);
}

.rk-series-say { flex: 1 1 24ch; max-width: 52ch; text-wrap: pretty; }


/* ==========================================================================
   16. SIGN IN
   ==========================================================================

   FM-52 chip C16, and the sign-in third of bug B49.

   Appended below everything above it for the reason sections 8 and 10 through
   15 each give in turn: more than one chip is open in this file at a time, so a
   rebase should be an append rather than a merge, and nothing above this line
   moved. Section 15 was the highest when this was written, so this is 16.

   THIS IS THE FIRST SECTION FOR A PAGE THAT IS NOT BEHIND THE `next-` GATE, and
   that is deliberate rather than an oversight. A gated sign-in page cannot be
   verified: `_is_internal_reader()` in scripts/serve.py requires a signed-in
   session, so `next-login.html` would 404 for exactly the people a sign-in page
   is for. login.html carries the redesign in place, and the header of that file
   has the rest of the reasoning.

   NO LIGHT THEME, AND NO TOKENS THAT FLIP. Public and logged-out pages are dark
   and carry no theme control, so this section reads the dark values through
   `[data-theme="dark"]`, which the page sets on `<html>` itself. What it does
   NOT do is redeclare a colour: every value below is a token section 1 already
   owns, including the two brand tokens the design's button is drawn in.

   THE BUTTON IS CHALK ON IRON AND NOT `--btn-bg`. That is the design's own
   sign-in button, and both halves are brand tokens section 1 already declares
   (`--chalk` and `--iron`), so it is not a ninth colour. It reads 9.4 against
   the card, where the site's blue `--btn-bg` in dark theme is tuned for the app
   and looks like a chip out of a screen the reader has not seen yet. `--chalk`
   had no user before this. It does now.

   EVERY CLASS HERE IS NEW and namespaced to the screen, which on this file is
   the only protection there is: `content_test.py` refuses a class a section
   heads that an earlier one owns, and `.rk-sub`, `.rk-msg` and `.rk-code` were
   all names this could have taken and all names another chip could want. */

/* The full-bleed ground. Darker than `--surface-0` on purpose: this is the one
   screen with nothing else on it, and the design lands it a step below the app
   so the card is the only thing lit. */
.rk-signin {
  min-height: 100vh;
  background: #0c0b08;
  padding: clamp(48px, 12vh, 150px) var(--gutter) 60px;
}

.rk-signin-col {
  max-width: 400px;
  margin-inline: auto;
}

/* `display: flex` and not the `inline-flex` `.rk-mark` carries, because
   `justify-content` on an inline box centres nothing: the box shrinks to its
   contents and sits wherever the text flow put it. Seen at 375px, where the
   lockup hugged the left edge while the card under it was centred. */
.rk-signin-mark {
  display: flex;
  justify-content: center;
  font-size: var(--t-card);
}

.rk-signin-card {
  margin-top: 28px;
  padding: 28px;
  background: var(--surface-1);
}

.rk-signin-title { font-size: var(--t-card); }

.rk-signin-sub {
  margin-top: 8px;
  color: var(--text-secondary);
  text-wrap: pretty;
}

.rk-signin-label { margin-top: 22px; }

.rk-signin-card input {
  width: 100%;
  box-sizing: border-box;
  margin-top: 8px;
  padding: 13px 14px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  background: var(--surface-0);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: var(--t-body);
  min-height: var(--tap);
}

/* The code box. Centred, spaced and monospaced, because six digits copied off a
   phone are read back one at a time rather than as a word. */
.rk-signin-code {
  font-family: var(--font-mono);
  font-size: var(--t-card);
  letter-spacing: .35em;
  text-align: center;
}

.rk-signin-go {
  width: 100%;
  margin-top: 14px;
  background: var(--chalk);
  border-color: var(--chalk);
  color: var(--iron);
}

.rk-signin-go:hover {
  background: var(--chalk);
  filter: brightness(1.06);
}

.rk-signin-back { margin-top: 16px; }

/* What the server said. js/auth-flow.js toggles `err` on it and owns the words:
   this only draws them. Empty until something has happened, so it takes no
   space of its own until it does. */
.rk-signin-msg:not(:empty) {
  margin-top: 14px;
  font-size: var(--t-small);
  color: var(--text-secondary);
  text-wrap: pretty;
}

.rk-signin-msg.err { color: var(--alert); }

.rk-signin-foot {
  margin-top: 16px;
  font-size: var(--t-micro);
  line-height: 1.55;
  color: var(--text-muted);
  text-wrap: pretty;
}

.rk-signin-foot code {
  font-family: var(--font-mono);
  color: var(--text-secondary);
}

.rk-signin-line {
  margin-top: 18px;
  text-align: center;
  font-size: var(--t-micro);
  color: var(--text-muted);
}

.rk-signin-legal {
  display: flex;
  justify-content: center;
  gap: 16px;
  margin-top: 10px;
  font-size: var(--t-micro);
}

.rk-signin-legal a { color: var(--text-muted); }


/* --------------------------------------------------------------------------
   The consent bar, on css/next.css

   IT LANDS WITH SIGN IN BECAUSE SIGN IN IS THE FIRST PAGE ON THIS FILE THAT
   DRAWS IT. js/cookie-notice.js appends the bar to `<body>` on every public
   page, and every rule it had lived in css/app.css, so the first page to move
   onto this stylesheet rendered a browser-default strip of unstyled text across
   the bottom of an otherwise finished screen. Nothing was red: the banner is
   built in JavaScript, the markup is correct, and no test on this site reads a
   computed style. It was seen in a browser, which is the whole reason this page
   was built in place rather than behind the gate.

   CHIP C15 REUSES THIS RATHER THAN DECLARING IT AGAIN. It is the chip that puts
   the banner on the ten app screens and redraws the three legal pages, so it is
   the chip that owns the component; it arrives one section early because the
   alternative was shipping a broken bar tonight.

   THE CLASS DOES NOT MOVE. It is `rk-consent`, never anything containing cookie,
   notice, banner or gdpr: those are WordPress-plugin selectors that content
   blockers hide by name, and a bar hidden by a blocker is a consent choice
   nobody was offered. That bug has been paid for once. See the note above the
   same rules in css/app.css and the header of js/cookie-notice.js.

   Z-INDEX 30, UNDER BOTH OVERLAYS, is carried over from app.css unchanged: the
   landing page's auth scrim is 40 and its modal is 60, so the notice sits behind
   the dim rather than floating over a dialog somebody is typing into. */

.rk-consent {
  position: fixed;
  z-index: 30;
  left: 12px;
  right: 12px;
  bottom: calc(12px + env(safe-area-inset-bottom, 0px));
  max-width: 1200px;
  margin-inline: auto;
  box-sizing: border-box;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 16px;
  padding: 14px 16px;
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-overlay);
  font-size: var(--t-small);
  color: var(--text-secondary);
}

.rk-consent-text { margin: 0; flex: 1 1 260px; line-height: 1.55; }
.rk-consent a { color: var(--accent); text-decoration: underline; }

/* Accept and Reject sit together. Accept is filled so it reads as the primary
   action, and Reject is a full button beside it rather than a downplayed link,
   so the two choices carry equal weight. That is the rule, not the styling. */
.rk-consent-actions { display: flex; flex-wrap: wrap; gap: 8px; flex: 0 0 auto; }

.rk-consent-btn {
  flex: 0 0 auto;
  cursor: pointer;
  font: inherit;
  font-weight: 500;
  color: var(--text-primary);
  background: var(--surface-1);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-pill);
  padding: 9px 18px;
  min-height: 38px;
}

.rk-consent-btn:hover { border-color: var(--accent); color: var(--accent); }

.rk-consent-btn-primary {
  background: var(--btn-bg);
  border-color: var(--btn-bg);
  color: var(--btn-fg);
}

.rk-consent-btn-primary:hover {
  filter: brightness(1.06);
  color: var(--btn-fg);
  border-color: var(--btn-bg);
}

/* On a phone the two buttons share their own full-width row as equal thumb
   targets, rather than words wedged beside four lines of prose. Written as a
   `min-width` query because this file has no `max-width` ones and
   foundations_test.py fails the build on one: the narrow layout is the default
   and the wide one is what is switched on. */
.rk-consent-actions { flex: 1 1 100%; }
.rk-consent-btn { flex: 1 1 0; }

@media (min-width: 521px) {
  .rk-consent-actions { flex: 0 0 auto; }
  .rk-consent-btn { flex: 0 0 auto; }
}


/* ==========================================================================
   17. THE THREE LEGAL PAGES
   ==========================================================================

   FM-52 chip C15, and the legal third of bug B49. The privacy policy, the
   cookie policy and the terms, moved onto this stylesheet in place.

   Appended below everything above it for the reason sections 8 and 10 through
   16 each give in turn: more than one chip is open in this file at a time, so a
   rebase should be an append rather than a merge, and nothing above this line
   moved. Section 16 was the highest when this was written, so this is 17.

   NOT BEHIND THE GATE, for the same reason sign in is not: a policy page is
   read by people who have no account, so a gated copy would 404 for exactly
   them. Connect IQ's reviewer reads these three without signing in, and their
   addresses are printed in the store listing.

   WHAT THIS SECTION IS, AND WHAT IT IS NOT. It is a prose layer, and it is the
   only one on this stylesheet that has to carry a document rather than a
   screen: a numbered agreement with headings, sub-headings, tables, asides and
   a great deal of running text. Section 9 has `.rk-prose` for an article, which
   is a different shape (one voice, few tables, no clause structure), so this
   does not try to reuse it and does not touch it either.

   WHAT IT DELIBERATELY DOES NOT ADD is a colour, a type step or a surface. The
   old `.policy` layer in css/app.css wrote nineteen hard-coded sizes and eight
   `rgba(242, 240, 233, …)` inks of its own, which is exactly what C1 replaced.
   Every value below is a token section 1 already owns. That is the whole of the
   redesign here: the words are unchanged, and they are set in the site's own
   type and colour rather than in a private copy of them.

   THE 3PX LEFT EDGE IS NOT USED ON ANY OF THIS. The design bundle's README says
   "never a coloured left-border accent on a card", and Design amended that on
   2026-08-01 to permit it on the STATE BANNER COMPONENT ONLY, never a card, a
   tile, a list row, a well or a table row. The aside below (`.rk-policy .note`)
   is a well, and the short-version block is a card, so neither takes one. The
   README on disk is stale and does not carry the amendment; the boundary is
   what to follow. */

.rk-policy > h1 {
  font-size: var(--t-page);
  line-height: 1.06;
  letter-spacing: var(--ls-head);
  margin-bottom: 10px;
}

/* When the policy was last touched. Mono because it is a date somebody checks
   against another date rather than reads. */
.rk-policy > .updated {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  color: var(--text-muted);
  margin-bottom: 26px;
}

.rk-policy > .lede {
  font-size: var(--t-lede);
  line-height: 1.6;
  color: var(--text-secondary);
  margin-bottom: 28px;
  max-width: 62ch;
  text-wrap: pretty;
}

/* Section labels are eyebrows rather than headings sized down, which is the one
   thing the old layer got right and this keeps: an agreement read on a phone is
   navigated by scanning for the next label, and a label wants to be a different
   KIND of thing from the prose rather than a larger version of it. The space
   above them is what sets the rhythm between clauses. */
.rk-policy > h2 {
  font-size: var(--t-micro);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  color: var(--oxide-lift);
  margin: 48px 0 14px;
}

.rk-policy > h3 {
  font-size: var(--t-body);
  font-weight: 500;
  color: var(--text-primary);
  margin: 26px 0 8px;
}

/* `--t-body` and not the inherited `--t-small`, which is what an app screen
   sets and what these pages picked up when they moved onto this file. A screen
   is scanned and an agreement is read: sixteen pixels is the step the old layer
   set these in and the one this much running text needs. Measured on the cookie
   page, where the whole document came out a step small. */
.rk-policy p {
  margin: 0 0 16px;
  font-size: var(--t-body);
  line-height: 1.62;
  color: var(--text-secondary);
  max-width: 68ch;
  text-wrap: pretty;
}

.rk-policy ul,
.rk-policy ol { margin: 0 0 16px; padding-inline-start: 20px; max-width: 68ch; }

.rk-policy li {
  margin-bottom: 10px;
  font-size: var(--t-small);
  line-height: 1.55;
  color: var(--text-secondary);
}

.rk-policy b,
.rk-policy strong { font-weight: 500; color: var(--text-primary); }

/* The rare italic aside is set in the voice face, as the design asks. */
.rk-policy em {
  font-family: var(--font-voice);
  font-style: italic;
  color: var(--text-primary);
}

/* The token and not the `0.88em` the old layer wrote. A mono face at the body
   step reads a shade large against the sans around it, which is what that
   relative size was for, and `--t-small` is the step below: one number the whole
   site already draws rather than a ratio only this file knows. */
.rk-policy code {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  color: var(--text-primary);
}

/* The short-version block at the top of each page, and the same card every
   other screen on this site uses. No left edge: see the note at the head of
   this section. */
.rk-policy .card {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  padding: 20px 22px;
  margin-bottom: 8px;
}

.rk-policy .card p:last-child,
.rk-policy .card ul:last-child { margin-bottom: 0; }

/* An aside about something that has already changed, or a caveat that would
   interrupt the clause it belongs to. A well rather than a banner: it is not a
   state, so it takes no tone colour and no edge. */
.rk-policy .note {
  background: var(--surface-1);
  border-radius: var(--r);
  padding: 14px 16px;
  font-size: var(--t-small);
  color: var(--text-muted);
  max-width: 68ch;
}

/* THE TABLES NEVER SCROLL SIDEWAYS, AND NOR DOES THE PAGE. Every cell here is
   prose, so the cells wrap and the table is as tall as it needs to be. A
   document whose reading column has to be dragged back into view line by line
   is worse than a long one, and this is a document.

   NARROW FIRST, like everything else in this file: below the breakpoint each
   row is a block and each cell labels itself out of its own `data-label`, so
   four columns of cookie facts read as four lines rather than as four words per
   line. The real table is what is switched ON at 521px, which is why this is a
   `min-width` query: `foundations_test.py` fails the build on a `max-width`
   one, and the rule it is protecting is exactly this, that the small screen is
   the default rather than the exception. */
.rk-policy .table-wrap {
  margin: 4px 0 16px;
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  overflow: hidden;
}

.rk-policy table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--t-small);
}

.rk-policy thead {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

.rk-policy tr {
  display: block;
  padding: 12px 16px;
  border-top: 1px solid var(--border);
}

.rk-policy tr:first-child { border-top: 0; }

.rk-policy td {
  display: block;
  padding: 0 0 4px;
  color: var(--text-secondary);
  line-height: 1.5;
  vertical-align: top;
  text-align: left;
}

.rk-policy td:last-child { padding-bottom: 0; }

/* The cell that names the thing. Ink rather than secondary, because a reader
   scanning for `rk_session` is scanning this column. */
.rk-policy td:first-child { color: var(--text-primary); font-weight: 500; }

/* What the column heading would have said, on the cell itself, since the
   heading row is hidden at this width. */
.rk-policy td[data-label]::before {
  content: attr(data-label) ": ";
  color: var(--text-muted);
}

@media (min-width: 521px) {
  .rk-policy thead {
    position: static;
    width: auto;
    height: auto;
    clip-path: none;
  }

  .rk-policy tr { display: table-row; padding: 0; border-top: 0; }

  .rk-policy th {
    text-align: left;
    vertical-align: top;
    padding: 11px 18px;
    background: var(--surface-1);
    color: var(--text-muted);
    font-weight: 500;
    font-size: var(--t-micro);
    letter-spacing: var(--ls-eyebrow);
    text-transform: uppercase;
  }

  .rk-policy td {
    display: table-cell;
    padding: 16px 18px;
    border-top: 1px solid var(--border);
  }

  .rk-policy td[data-label]::before { content: none; }
}

/* The one control on any of these three pages: the cookie page's way back to
   the banner, with the stored answer beside it. */
.rk-policy .cookie-choice-row {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}

.rk-policy .cookie-choice {
  font-size: var(--t-small);
  color: var(--text-muted);
}

.rk-policy .cookie-choice span { color: var(--text-primary); }


/* ==========================================================================
   18. THE LANDING PAGE, AND ONBOARDING
   ==========================================================================

   FM-52 chip C19, and the last two thirds of bug B49. The page a stranger
   arrives on and the seven questions a brand-new athlete answers, moved onto
   this stylesheet in place.

   Appended below everything above it for the reason sections 8 and 10 through
   17 each give in turn: more than one chip is open in this file at a time, so a
   rebase should be an append rather than a merge, and nothing above this line
   moved. Section 17 was the highest when this was written, so this is 18.

   TWO SCREENS IN ONE SECTION, and they are not neighbours by accident. They are
   the two surfaces on this site with no gate in front of them and no redesigned
   counterpart, they are the only two an unconvinced reader and a five-minute-old
   account ever see, and Jason ruled on 2026-08-02 that both land before cutover
   while the admin console does not. A section each would have been two headers
   saying the same paragraph.

   NEITHER IS BEHIND THE `next-` GATE, for the reason section 16 gives about
   sign in. `_is_internal_reader()` in scripts/serve.py wants a signed-in session
   whose address is listed, so a gated landing page 404s for exactly the
   strangers it is for, and a gated onboarding 404s for the new athletes it
   exists to serve. Built in place, both can be driven, which is the only way
   either gets looked at at all.

   THE LANDING PAGE IS DARK AND PINNED. Public and logged-out pages carry no
   theme control, so index.html sets `data-theme="dark"` on the root itself and
   this half of the section reads the dark values through it. ONBOARDING IS NOT:
   it is signed in, it takes the athlete's stored theme, and every rule below it
   is written in tokens that flip. Two different answers for two different
   readers, in one section, so the difference is visible rather than inferred.

   NO NEW COLOUR, NO NEW TYPE STEP, NO NEW COLUMN CAP. Everything here is a
   token section 1 owns and a cap section 2 declares, `1120` for the public shell
   and `760` for a page whose whole job is one question at a time. The landing
   page's own button is `--chalk` on `--iron`, which is the design's and is what
   section 16 already settled for sign in rather than a ninth colour.

   WHAT IT REUSES RATHER THAN REDRAWING. `.rk-mark`, `.rk-btn`, `.rk-eyebrow`,
   `.rk-lede`, `.rk-card`, `.rk-col` and `.rk-num` are C1's and C5's;
   `.rk-signin-card`, `.rk-signin-title`, `.rk-signin-sub`, `.rk-signin-label`,
   `.rk-signin-code`, `.rk-signin-go`, `.rk-signin-msg` and `.rk-signin-foot` are
   section 16's. This page wore them too, on an auth overlay it opened over
   itself, because that overlay WAS the sign-in card. The overlay went with the
   origin split, and this section's last block is its tombstone, so /login is the
   only thing wearing them now and there is no second copy of the card to drift.
   `.rk-sitefoot` is section 9's one-row strip, reused
   for the same reason: the app's four-column footer in js/screen-chrome.js
   points at ten gated addresses, so a stranger would meet four columns of 404s.

   EVERY CLASS THIS SECTION DECLARES IS NEW, and on this file that is the only
   protection there is. content_test.py refuses a class a section heads that an
   earlier one owns, and `.rk-foot`, which section 5 owns and section 9 took, is
   why: both rules were valid, the later lost the cascade on `max-width`, and a
   footer rendered 645px wide in a 1265px window with every test green. So this
   section owns `rk-home-*`, `rk-pubbar-*` and `rk-ob-*` and heads a rule with
   nothing else.

   WHAT ONBOARDING DOES NOT GET, and it is a decision rather than an omission.
   js/screen-chrome.js is the redesign's shell and this page does not load it:
   every entry in its bar and its footer is a `next-` address, so a brand-new
   athlete would be handed ten links that 404 for them, and its account menu
   offers Setup and Account to somebody who has not finished answering the seven
   questions those screens edit. What is drawn instead is a mark and a footer,
   which is what a screen with one job needs. The cost is that the theme control
   lives in that menu, so an athlete cannot switch theme mid-onboarding: a
   preference already stored still applies, because js/theme.js is imported by
   js/onboarding.js and always was. */


/* --------------------------------------------------------------------------
   The public bar

   Sticky, because the page is long and the way in should not be something a
   reader has to scroll back up for. Translucent over the page ground with a
   blur, which is the design's, and the fallback in a browser without
   `backdrop-filter` is an opaque bar rather than an unreadable one: the colour
   is a solid `--surface-0` and the blur only softens what passes under it. */

.rk-pubbar {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--surface-0);
  border-bottom: 1px solid var(--border);
}

@supports (backdrop-filter: blur(8px)) or (-webkit-backdrop-filter: blur(8px)) {
  .rk-pubbar {
    background: color-mix(in srgb, var(--surface-0) 88%, transparent);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
  }
}

.rk-pubbar-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px 16px;
  max-width: 1120px;
  margin-inline: auto;
  padding: 10px var(--gutter);
}

.rk-pubbar-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

/* The one entry that is a link rather than an action. It reads as navigation
   and not as a third button, so it carries no border and takes the tap height
   from its own padding. */
.rk-pubbar-quiet {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  padding: 0 12px;
  color: var(--text-secondary);
  font-size: var(--t-small);
}

.rk-pubbar-quiet:hover { color: var(--text-primary); text-decoration: none; }

/* The page's own primary action, in the design's chalk on iron. Section 16
   settled this pair for sign in and the argument is the same here: the site's
   blue `--btn-bg` is the app's action colour and reads on a commercial page as
   a chip out of a screen the reader has not seen yet. */
.rk-home-go {
  background: var(--chalk);
  border-color: var(--chalk);
  color: var(--iron);
}

.rk-home-go:hover {
  background: var(--chalk);
  color: var(--iron);
  filter: brightness(1.06);
}


/* --------------------------------------------------------------------------
   The hero, and the week beside it

   One column on a phone and two from 1100px, which is later than the site's
   other two-column blocks switch. The right-hand card is a table of loads: at
   700px it would be four lifts squeezed into half a tablet, and the words are
   the page. */

.rk-home-hero { display: grid; gap: 40px; align-items: center; }

.rk-home-title { margin-top: 16px; text-wrap: pretty; }
.rk-home-lede { margin-top: 20px; max-width: 52ch; text-wrap: pretty; }

.rk-home-cta {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 28px;
}

.rk-home-fine { margin-top: 16px; max-width: 46ch; text-wrap: pretty; }

.rk-home-week { overflow: hidden; }

.rk-home-week-head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: 6px 12px;
  padding: 15px 18px;
  border-bottom: 1px solid var(--border);
}

/* THE WARM ACCENT IN THE DELIVERED DESIGN IS THE SITE'S BLUE HERE, and that is
   C1's decision rather than this chip's. The bundle's `--accent` is `#e07a4f`
   and it colours every eyebrow, figure and gain on the public prototype; this
   site's action accent is `#185fa5`, with oxide kept for the mark and warm
   highlights, and the handover records that as settled rather than as an unfixed
   defect. `--record` is oxide and is NOT the token for a load that went up: it
   means a personal record and section 8 uses it for exactly one pill. So a lift
   whose bar moved takes `--accent`, which is the tone the redesigned week gives
   a gain verdict, and this card reads the way the app it is advertising does. */
.rk-home-week-sum { font-size: var(--t-micro); color: var(--accent); }

/* One lift: its name and its load on the first line, the scheme and what
   changed under them. `1fr auto` rather than four columns, so a long movement
   name wraps inside its own cell instead of pushing the load off the card. */
.rk-home-lift {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 3px 14px;
  padding: 12px 18px;
  border-bottom: 1px solid var(--border);
  font-size: var(--t-small);
}

.rk-home-lift-load { white-space: nowrap; }
.rk-home-lift-chg { color: var(--accent); text-align: end; white-space: nowrap; }

.rk-home-week-why { padding: 14px 18px; text-wrap: pretty; }


/* --------------------------------------------------------------------------
   A band, and the three cards in it

   The band is the one full-bleed element on the page, so it takes the tint
   itself and the column inside it keeps the gutters. */

.rk-home-band {
  background: var(--surface-1);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}

/* The bar at the top of this page is sticky, so an in-page link that scrolls to
   a band would put its first line underneath it. `scroll-margin-top` on the
   target rather than `scroll-padding-top` on the root, because the root is
   shared with every other page on this stylesheet and only this one has a bar
   that follows the reader down. */
.rk-home-band[id] { scroll-margin-top: 76px; }

.rk-home-cards { display: grid; gap: 16px; }

.rk-home-card { padding: 24px; }

/* The icon in its own tinted disc, which is `.rk-howto`'s device in section 9
   rather than a new one. A bare glyph at `--i-lg` above a 24px heading reads as
   something that failed to load; the disc gives it a size the scale does not
   have to grow a step to supply. */
.rk-home-card > i {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: var(--accent-bg);
  color: var(--accent);
  font-size: var(--i-lg);
}

.rk-home-card > h2 { margin-top: 14px; text-wrap: pretty; }
.rk-home-card > p { margin-top: 10px; text-wrap: pretty; }

/* The heading a band opens with. `--t-fig` rather than `--t-card`, because a
   section on a page whose h1 is 64px needs a step between the two. */
.rk-home-h2 { margin-top: 12px; text-wrap: pretty; }


/* --------------------------------------------------------------------------
   The watch band

   Not a photograph of a watch: an actual capture off an epix 2 Pro,
   round-cropped because the display is round and the capture is square with
   black corners. It sits on `--iron`, the brand's own dark, so the bezel reads
   as a bezel rather than as a hole in the card. */

.rk-home-watch { display: grid; gap: 36px; align-items: center; }

.rk-home-face {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 34px;
  border-radius: var(--r-card);
  background: var(--iron);
}

.rk-home-face img {
  width: min(244px, 70vw);
  height: auto;
  border-radius: 50%;
  border: 7px solid var(--graphite);
  object-fit: cover;
}

/* Garmin's own artwork, unaltered, which their brand guidelines require. The
   badge ground is near black, so the hover lift has to be larger than it would
   be on white artwork to register at all. */
.rk-home-ciq {
  display: inline-block;
  margin-top: 24px;
  line-height: 0;
  border-radius: 7px;
}

.rk-home-ciq img { display: block; width: 155px; height: auto; }
.rk-home-ciq:hover { filter: brightness(1.18); }


/* --------------------------------------------------------------------------
   The founding band

   The count sentence is `--text-muted` rather than a figure, and that is the
   product decision rather than a styling one: below 21 founding members
   `countLine()` in js/founder.js returns nothing at all and the line is the
   entitlement on its own. There is nothing here sized for a number, because
   most of the time there is no number.

   NO RECENCY LINE, AND NO CLASS FOR ONE. The delivered design draws "One logged
   session in any twelve months keeps all of it" and a tick reading "Last session
   3 days ago" on the founder card. The grant is unconditional, so nothing in the
   product may imply it is at risk, and founder_test.py refuses such a line in
   js/founder.js and in this file both. */

.rk-home-found { display: grid; gap: 32px; align-items: center; }

.rk-home-found-body { margin-top: 14px; max-width: 56ch; text-wrap: pretty; }
.rk-home-count { margin-top: 16px; max-width: 46ch; text-wrap: pretty; }

.rk-home-plate {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 26px;
}

.rk-home-plate img { display: block; flex: none; width: 72px; height: 72px; }
.rk-home-plate p + p { margin-top: 6px; }


/* --------------------------------------------------------------------------
   The sign-in overlay, and why there are no rules here

   `.rk-scrim`, `.rk-home-dialog`, `.rk-home-dialog-title`, `.rk-home-x` and
   `.rk-home-swap` stood here until the origin split (FM-52 chip C12b, second
   half). They styled an overlay the landing page opened over itself to sign
   somebody in, and that overlay cannot exist any more, so the rules went with
   the markup rather than sitting here waiting to collide with a name somebody
   else wants.

   IT IS NOT COMING BACK, AND THE REASON IS NOT A PREFERENCE. /auth/verify sets
   the session cookie on whatever host answered it, the cookie carries no Domain
   and never will, and the app is on app.reckoner.fit. An overlay on the public
   site therefore signs an athlete in on a host the app cannot read a cookie
   from: a sign-in that succeeds and leaves you signed out, with no error on
   either side. The landing page links to /login instead, and the card there is
   section 16's, which is the card this overlay was reusing anyway.

   Rebuilding it would need a cross-origin token handoff, which is a design in
   its own right and not a stylesheet's business.

   THE ONE FACT THAT OUTLIVED THE RULES is the stacking order, because the two
   things it ordered are both still here. `.rk-modal` in section 8 is z-index 40
   and the consent bar is 30, so a notice never floats over a dialog somebody is
   typing into. That was written down here when the overlay was the taller of
   the two; section 8 is where it lives now.

/* --------------------------------------------------------------------------
   Wide

   Narrow first, like everything else in this file. The three cards go to a row
   at 700px because they are prose; the two blocks that pair text with a table
   or an image wait for 1100px, where there is room for both to be read. */

@media (min-width: 700px) {
  .rk-home-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .rk-home-watch { grid-template-columns: minmax(0, .8fr) minmax(0, 1fr); }
}

@media (min-width: 1100px) {
  .rk-home-hero { grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr); gap: 56px; }
  .rk-home-found { grid-template-columns: minmax(0, 1fr) minmax(0, .7fr); gap: 44px; }
}


/* --------------------------------------------------------------------------
   Onboarding

   ONE QUESTION ON THE SCREEN AT A TIME, in a 760px column, which is the cap
   this file already gives an article's body. The seven questions are the one
   place in the product where a reader is asked to think rather than to scan, so
   they get the reading column rather than the app's 820px settings column.

   THE CONTROLS ARE js/fields.js's AND ARE STYLED IN SECTION 8, under
   `.rk-editor`. That block was written by chip C5 for the redesigned Setup
   screen, and its own note says it is a compatibility layer with a known end:
   when the redesign took onboarding, the controls would move and the block would
   go with them. This is that chip and they have not moved, because the end that
   was expected was onboarding LEAVING those class names behind, and both screens
   still ask their questions through the same editors. Two editors for one
   question is two places a validation rule lives, and the one nobody is looking
   at is the one that goes wrong. So `#ob-body` wears `.rk-editor` and section 8
   dresses everything inside it. What is below is the frame around that. */

.rk-ob { min-height: 100vh; display: flex; flex-direction: column; }

.rk-ob-top {
  border-bottom: 1px solid var(--topbar-edge);
  background: var(--surface-2);
}

.rk-ob-top-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 16px;
  max-width: 760px;
  margin-inline: auto;
  padding: 10px var(--gutter);
}

.rk-ob-main { flex: 1 0 auto; }

.rk-ob-card { padding: 26px 24px 28px; }

/* The step's own heading. It is the QUESTION rather than the screen's name, so
   it takes the page step and changes on every step, which is why onboarding has
   no entry in states.js's HEADS: that table holds one fixed title per screen and
   this screen has seven. */
.rk-ob-title { margin-top: 10px; text-wrap: pretty; }

.rk-ob-prompt {
  margin-top: 10px;
  max-width: 56ch;
  color: var(--text-secondary);
  text-wrap: pretty;
}

.rk-ob-title + .rk-ob-prompt:empty { display: none; }

.rk-ob-card .rk-editor { margin-top: 24px; }

/* What the server refused, under the control it refused. Section 4 owns
   `.rk-field-error` for a field inside a form the redesign built; this is the
   one line the whole step shares, so it sits above the actions rather than under
   a named input. */
.rk-ob-error {
  margin-top: 16px;
  font-size: var(--t-small);
  color: var(--alert);
  text-wrap: pretty;
}

.rk-ob-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 28px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}

/* Continue sits at the end of the row and Back at the start, which is the
   design's and is the way round a thumb expects on a phone. `margin-inline-start:
   auto` on the primary rather than a spacer element, so a step with no Back
   button still puts Continue where the last one did. */
.rk-ob-go { margin-inline-start: auto; }

.rk-ob-foot {
  margin-top: 18px;
  max-width: 60ch;
  font-size: var(--t-small);
  color: var(--text-muted);
  text-wrap: pretty;
}

/* The resume notice, and the one line the page says before it has asked
   anything. A banner rather than an error: picking up where you left off is a
   fact about the flow, not a failure of it. `.rk-banner` is section 4's and
   wants a tone attribute and a body element, which this line has neither of, so
   it draws its own quieter version and heads a class of its own. */
.rk-ob-say {
  padding: 13px 16px;
  border-radius: var(--r);
  background: var(--surface-1);
  font-size: var(--t-small);
  color: var(--text-secondary);
  text-wrap: pretty;
}

.rk-ob-say.rk-ob-good { color: var(--success); }

.rk-ob-say + .rk-card { margin-top: 16px; }


/* --------------------------------------------------------------------------
   The phase rail

   Gate 1 walks in two visible phases, two steps of setup and then the five
   questions, and the rail is what makes the five literal rather than a relabel:
   bodyweight was once added as a sixth question while the copy went on promising
   five. The design draws one bar across the whole flow, which would take that
   back, so what is drawn instead is the design's bar once per phase.

   The dots are aria-hidden in the markup, because the count they carry is
   already in the eyebrow above them in words. */

/* The gap under the rail, drawn only when there is a rail. The refusal, the
   program and the returning list all empty this element, and a fixed margin
   would leave a hole above the eyebrow on all three. */
.rk-ob-prog:not(:empty) { margin-bottom: 18px; }

.rk-ob-rail {
  display: flex;
  align-items: flex-end;
  gap: 14px;
}

.rk-ob-grp { flex: 1 1 0; min-width: 0; }

/* Sentence case and NOT an eyebrow, which is the one thing that keeps this from
   reading as a mistake. The rail names the two phases and `#ob-eyebrow` under it
   names the step, and on a setup step both say the word Setup: copy.js supplies
   "Setup" for the rail and "SETUP" for the eyebrow, and drawing the first as an
   uppercase tracked label made them look like the same label printed twice. They
   are two different things, so they are drawn as two different things: a caption
   on a map, and the marker for where you are on it. The words are copy.js's and
   this chip did not touch them. */
.rk-ob-lab {
  font-size: var(--t-micro);
  color: var(--text-muted);
  margin-bottom: 7px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.rk-ob-grp.active .rk-ob-lab { color: var(--accent); }

.rk-ob-dots { display: flex; gap: 4px; }

.rk-ob-dot {
  flex: 1 1 0;
  height: 4px;
  border-radius: 2px;
  background: var(--border-strong);
}

.rk-ob-dot.done { background: var(--text-muted); }
.rk-ob-dot.on { background: var(--accent); }


/* --------------------------------------------------------------------------
   The three screens that are not a question

   The under-13 refusal, the program that lands when gate 1 closes, and the
   returning athlete's confirmation list. All three replace the body of the same
   card, so none of them is a screen of its own and none needs a shell. */

.rk-ob-note { margin-top: 14px; max-width: 60ch; text-wrap: pretty; }
.rk-ob-hint { margin-top: 14px; max-width: 60ch; font-size: var(--t-small); color: var(--text-muted); text-wrap: pretty; }

.rk-ob-cleared { margin-top: 14px; color: var(--success); font-size: var(--t-small); }
.rk-ob-eligible { margin-top: 14px; color: var(--text-secondary); font-size: var(--t-small); text-wrap: pretty; }

.rk-ob-refuse-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 22px;
}

/* The week, the moment it exists. A list of days rather than a plan: the plan
   is on the next screen and this is the receipt for finishing. */
.rk-ob-days { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }

.rk-ob-days li {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  align-items: baseline;
  padding: 13px 16px;
  background: var(--surface-1);
  border-radius: var(--r);
}

.rk-ob-days b { font-weight: 500; }
.rk-ob-days span { font-size: var(--t-small); color: var(--text-secondary); }

/* What Reckoner already knows, for somebody coming back. Each row is a fact and
   a way to change it, which is the whole of the returning flow: confirm, do not
   re-interview. */
.rk-ob-confirm { list-style: none; margin: 0; padding: 0; }

.rk-ob-confirm li {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  align-items: baseline;
  padding: 13px 0;
  border-top: 1px solid var(--border);
}

.rk-ob-confirm li:first-child { border-top: 0; }

.rk-ob-confirm-label {
  flex: 0 0 auto;
  min-width: 132px;
  font-size: var(--t-small);
  color: var(--text-muted);
}

.rk-ob-edit { margin-inline-start: auto; font-size: var(--t-small); }


/* ==========================================================================
   19. THE PUBLIC FOUNDING MEMBERS PAGE
   ==========================================================================

   FM-52 chip C24. /founding: the page that holds the full terms of the founding
   hundred, for somebody who is not signed in and may never have been.

   Appended below everything above it for the reason sections 8 and 10 through
   18 each give in turn: more than one chip is open in this file at a time, so a
   rebase should be an append rather than a merge, and nothing above this line
   moved. Section 18 was the highest when this was written, so this is 19.

   IT IS DARK AND PINNED, like every public page. founding.html sets
   `data-theme="dark"` on the root and this section reads the dark values
   through it. There is no theme control on a logged-out page, because the
   switch is a signed-in feature.

   WHAT IT REUSES RATHER THAN REDRAWING, and the list is long on purpose: this
   is the fourth public page and it should not be the first one that looks like
   somebody else built it. `.rk-pubbar`, `.rk-pubbar-inner`, `.rk-pubbar-actions`,
   `.rk-pubbar-quiet`, `.rk-home-go`, `.rk-home-cta` and `.rk-home-band` are
   section 18's, off the landing page, which is the page a reader most likely
   arrived from. `.rk-sitefoot` is section 9's one-row strip, for the reason the
   landing page gives: every address in the app's four-column footer is a
   signed-in screen, so a stranger would meet four columns of sign-in prompts.
   `.rk-col`, `.rk-card`, `.rk-btn`, `.rk-eyebrow`, `.rk-hero`, `.rk-fig`,
   `.rk-lede` and `.rk-num` are C1's.

   EVERY CLASS THIS SECTION DECLARES IS NEW AND STARTS `rk-fnd-`, and on this
   file that is the only protection there is. `rk-founder-` is section 6's, and
   it is the BADGE: the component a signed-in screen draws for one athlete's own
   grant. This is the marketing page about the grant, they will appear on the
   same site and never on the same screen, and one prefix for both is how a rule
   written for one silently restyles the other. content_test.py refuses a class
   this section heads that an earlier one owns.

   TWO THINGS THE DESIGN DRAWS THAT ARE NOT DRAWN HERE, both held for Design
   rather than absorbed:

   1. **The two big figures are `--t-hero`, which is 44px.** The prototype draws
      the plate's number and the count at 64px and above. This file has eight
      type steps and no rule may write a bare px `font-size`, so a bigger figure
      is a NINTH STEP, which is a decision made once in section 1 and inherited
      by every screen on the site. That is not a decision a marketing page gets
      to make on its own. Both figures are the largest step the scale has.

   2. **No count is drawn below the reveal threshold**, and the card that stands
      in its place is the whole of `.rk-fnd-count` as it ships in the markup.
      See js/founding.js. There is nothing here sized for a number most of the
      time, because most of the time there is no number. */


/* --------------------------------------------------------------------------
   The hero, and the card beside it

   Narrow first, like everything else in this file. The card waits for 1100px
   before it sits beside the words, because both of them are read rather than
   scanned and neither survives a half-width column. */

.rk-fnd-hero { display: grid; gap: 40px; align-items: start; }

.rk-fnd-title { margin-top: 16px; text-wrap: pretty; }
.rk-fnd-lede { margin-top: 20px; max-width: 56ch; text-wrap: pretty; }
.rk-fnd-fine { margin-top: 16px; max-width: 46ch; text-wrap: pretty; }

.rk-fnd-count { padding: 26px; }

.rk-fnd-count-head {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-top: 16px;
}

.rk-fnd-count-head img { display: block; flex: none; width: 56px; height: 56px; }
.rk-fnd-count-head p + p { margin-top: 3px; }

.rk-fnd-count-note { margin-top: 18px; text-wrap: pretty; }

/* The figure, once there is one. Tabular so it does not shuffle if it is ever
   redrawn in place, and `--accent` because it is the one number on the page and
   the page is otherwise all prose. */
.rk-fnd-count-n {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-top: 14px;
}

.rk-fnd-count-n span {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-hero);
  font-weight: 500;
  line-height: 1;
  letter-spacing: var(--ls-display);
  color: var(--accent);
}

.rk-fnd-count-n small {
  font-size: var(--t-lede);
  color: var(--text-secondary);
}

/* A ticked list, used three ways on this page: the facts in the pre-reveal
   card, the points under each deal card, and nothing else. The tick is
   `--accent` and the words are secondary, so a column of them reads as a list
   rather than as six competing lines. */
.rk-fnd-facts,
.rk-fnd-points {
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
}

.rk-fnd-facts li,
.rk-fnd-points li {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 8px 0;
  font-size: var(--t-small);
  color: var(--text-secondary);
  text-wrap: pretty;
}

.rk-fnd-facts li { border-top: 1px solid var(--border); }
.rk-fnd-points li { border-top: 1px solid var(--border); }

.rk-fnd-facts li > i,
.rk-fnd-points li > i {
  flex: none;
  margin-top: 2px;
  font-size: var(--i-sm);
  color: var(--accent);
}


/* --------------------------------------------------------------------------
   The three deal cards

   Free forever, paid one day, what a founder keeps. They go to a row at 700px
   because they are prose, which is where section 18 puts the landing page's
   three for the same reason. */

.rk-fnd-deal { display: grid; gap: 16px; }

.rk-fnd-deal-card { padding: 24px; }

.rk-fnd-deal-card > i {
  display: block;
  font-size: var(--i-lg);
  color: var(--accent);
}

.rk-fnd-h3 {
  margin-top: 14px;
  font-size: var(--t-card);
  font-weight: 500;
  line-height: var(--lh-head);
  letter-spacing: var(--ls-head);
}

.rk-fnd-deal-card > p { margin-top: 10px; text-wrap: pretty; }


/* --------------------------------------------------------------------------
   What we ask in return

   A definition list rather than a stack of divs, because that is what it is:
   four things asked for, each with what it means. A screen reader gets the
   pairing for free and the markup says what the rows are. */

.rk-fnd-ask { display: grid; gap: 32px; align-items: start; }

.rk-fnd-h2 { margin-top: 12px; text-wrap: pretty; }
.rk-fnd-ask-lede { margin-top: 14px; max-width: 52ch; text-wrap: pretty; }

.rk-fnd-rows { margin: 0; }

.rk-fnd-rows > div {
  padding: 16px 0;
  border-top: 1px solid var(--border);
}

.rk-fnd-rows dt { color: var(--text-primary); }

.rk-fnd-rows dd {
  margin: 5px 0 0;
  font-size: var(--t-small);
  color: var(--text-secondary);
  text-wrap: pretty;
}


/* --------------------------------------------------------------------------
   The mark, and the plate

   THE PLATE IS THE ONE PLACE ON THIS PAGE THE GROUND INVERTS, and it is a fixed
   pair rather than a token pair on purpose. It is the printed artefact the
   number is imagined on: chalk with iron on it, the same whoever is reading and
   whatever the page around it is doing. `--chalk` and `--iron` are section 1's
   brand tokens and do not flip with the theme, which is exactly why they are
   the right two here and the wrong two anywhere a theme is meant to reach.

   THE NUMBER IS `--oxide` ON `--chalk`, WHICH IS 4.13 AND IS ONLY LEGAL
   BECAUSE IT IS LARGE. WCAG AA wants 4.5 for body text and 3.0 for large text,
   and `--t-hero` at weight 500 is large by either definition. So the oxide
   stays on the figure and nothing else on this plate: the labels, the name and
   the date are all iron at reduced opacity, which is the design's own
   arrangement and clears AA on its own. Do not reach for oxide for a caption
   here. foundations_test.py grades TOKENS against the four surfaces and cannot
   see a fixed pair inside a component, so this paragraph is the check. */

.rk-fnd-mark { display: grid; gap: 40px; align-items: center; }

.rk-fnd-mark-lede { margin-top: 14px; max-width: 52ch; text-wrap: pretty; }

.rk-fnd-mark-body {
  margin-top: 14px;
  max-width: 56ch;
  line-height: var(--lh-body);
  text-wrap: pretty;
}

.rk-fnd-plate {
  background: var(--chalk);
  color: var(--iron);
  border-radius: var(--r-card);
  overflow: hidden;
}

.rk-fnd-plate-top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 16px;
  padding: 26px 28px 0;
}

.rk-fnd-plate-top img { display: block; flex: none; width: 60px; height: 60px; }

.rk-fnd-plate-eyebrow,
.rk-fnd-plate-label {
  display: block;
  font-size: var(--t-micro);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: color-mix(in srgb, var(--iron) 62%, transparent);
}

.rk-fnd-plate-site {
  display: block;
  margin-top: 5px;
  font-size: var(--t-micro);
  color: color-mix(in srgb, var(--iron) 62%, transparent);
}

.rk-fnd-plate-n {
  display: flex;
  align-items: flex-end;
  gap: 16px;
  padding: 10px 28px 22px;
}

.rk-fnd-plate-n span {
  font-size: var(--t-hero);
  font-weight: 500;
  line-height: 1;
  letter-spacing: var(--ls-display);
  font-variant-numeric: tabular-nums;
  color: var(--oxide);
}

.rk-fnd-plate-n small {
  font-size: var(--t-body);
  color: color-mix(in srgb, var(--iron) 62%, transparent);
}

.rk-fnd-plate-grant {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 8px 20px;
  padding: 16px 28px;
  border-top: 1px solid color-mix(in srgb, var(--iron) 22%, transparent);
}

.rk-fnd-plate-right { text-align: end; }

.rk-fnd-plate-val {
  display: block;
  margin-top: 3px;
  font-size: var(--t-lede);
  color: var(--iron);
}

/* The one line on the site set in the voice face, and it earns it: it is the
   citation on a receipt rather than a piece of interface. */
.rk-fnd-plate-quote {
  padding: 14px 28px 18px;
  border-top: 1px solid color-mix(in srgb, var(--iron) 22%, transparent);
  font-family: var(--font-voice);
  font-style: italic;
  font-size: var(--t-body);
  line-height: 1.5;
  color: color-mix(in srgb, var(--iron) 78%, transparent);
}

/* The same mark at the two sizes it has to survive beside a name. Hairline gaps
   between the rows rather than borders, which is how the design draws it: the
   grid's own background shows through as the rule. */
.rk-fnd-sizes {
  list-style: none;
  display: grid;
  gap: 1px;
  margin: 14px 0 0;
  padding: 0;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  overflow: hidden;
}

.rk-fnd-sizes li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 14px 18px;
  background: var(--surface-2);
}

.rk-fnd-sizes li > span:first-child {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--t-small);
  color: var(--text-secondary);
}

.rk-fnd-sizes img { display: block; flex: none; }
.rk-fnd-sizes li > span:last-child { flex: none; }

.rk-fnd-sizes-note { margin-top: 12px; text-wrap: pretty; }


/* --------------------------------------------------------------------------
   The small print

   THE THREE TERMS FIRST, THEN THE QUESTIONS. The list is `TERMS` in
   js/founder.js word for word, written into the markup so it does not wait on a
   fetch, and it is the same three statements chip C23 puts on the athlete's own
   founding screen. `--text-primary` rather than secondary: they are the deal
   rather than a note about it, and they are the reason this page exists.

   The questions go to two columns at 1100px. They are short answers, so a
   reader scans for theirs rather than reading down, and one column of seven at
   full width is a long way to scroll past six answers you did not want. */

.rk-fnd-terms {
  list-style: none;
  margin: 20px 0 0;
  padding: 0;
  display: grid;
  gap: 12px;
}

.rk-fnd-terms li {
  max-width: 64ch;
  padding-inline-start: 16px;
  border-inline-start: 2px solid var(--accent);
  color: var(--text-primary);
  text-wrap: pretty;
}

.rk-fnd-faq {
  display: grid;
  gap: 0 44px;
  margin: 32px 0 0;
}

.rk-fnd-faq > div {
  padding: 18px 0;
  border-top: 1px solid var(--border);
}

.rk-fnd-faq dt {
  font-size: var(--t-lede);
  color: var(--text-primary);
  text-wrap: pretty;
}

.rk-fnd-faq dd {
  margin: 8px 0 0;
  max-width: 56ch;
  color: var(--text-secondary);
  line-height: var(--lh-body);
  text-wrap: pretty;
}

/* The closing strip. An accent rule above it rather than a border, because it
   is the end of the argument rather than another row of it. */
.rk-fnd-close {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 20px;
  margin-top: 36px;
  padding-top: 28px;
  border-top: 1px solid var(--accent);
}

.rk-fnd-close-line {
  font-size: var(--t-card);
  line-height: var(--lh-head);
  letter-spacing: var(--ls-head);
  text-wrap: pretty;
}

.rk-fnd-close p + p { margin-top: 6px; }


/* --------------------------------------------------------------------------
   Wide

   The same two steps section 18 uses, and for the same reasons. Prose cards go
   to a row at 700px; a block that pairs text with a card or a plate waits for
   1100px, where there is room for both to be read. */

@media (min-width: 700px) {
  .rk-fnd-deal { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (min-width: 1100px) {
  .rk-fnd-hero { grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr); gap: 48px; }
  .rk-fnd-ask { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 44px; }
  .rk-fnd-mark { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 44px; }
  .rk-fnd-faq { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}


/* ==========================================================================
   20. THE ATHLETE'S OWN FOUNDING SCREEN
   ==========================================================================

   FM-52 chip C23, appended below everything before it, for the reason every
   section since C4 gives: more than one chip is in this file at a time, so a
   rebase should be an append rather than a merge. Nothing above this line moved,
   and this section declares no token of its own.

   The screen is /grant: a receipt, the terms stated once, what the athlete asked
   for, and a line to the public page. js/screen-grant.js builds all of it.

   IT REUSES SECTION 6'S CARD RATHER THAN DRAWING A SECOND ONE. The grant card's
   shape IS the badge's shape (the mark at 84px, a body beside it, wrapping to
   put the mark above the text on a narrow screen), so `.rk-founder` and
   `.rk-founder-mark` do that work and what is added here is the two lines inside
   it, which are a date and a paragraph rather than the badge's number and date.

   NOT DRAWN, ON PURPOSE, and this is the same absence section 6 states: there is
   no class here for a tick, a status line, a last-session date, a countdown or
   progress toward anything. The grant is unconditional and for life. A screen
   built around it that drew a measurement beside it would say the opposite
   whatever the words were, and founder_test.py refuses one in this section as
   well as in section 6. Adding one is the thing to argue about rather than the
   thing to style.

   THE CLASS NAMES WERE CHECKED AGAINST THE WHOLE FILE BEFORE THEY WERE CHOSEN.
   `.rk-foot` collided once between sections 5 and 9, both rules were valid, the
   later one lost the cascade and a footer rendered 645px wide in a 1265px window
   with every test green. `rk-grant-` appears nowhere above this line. */

/* The one row in the account menu with an image rather than an icon. It is here
   rather than in section 11 because this chip is what put it there, and the
   append rule is worth more than filing it beside the menu it belongs to.

   `--i-lg` is 18px, which is the size Design draws the mark at on this row AND
   the size every icon in that menu is already drawn at, so the two kinds of
   leading glyph line up by construction rather than by two numbers agreeing.
   `flex: none` for the reason the icons have it: the label reflows, the mark
   does not. */
.rk-menu-mark {
  width: var(--i-lg);
  height: var(--i-lg);
  display: block;
  flex: none;
}


/* --------------------------------------------------------------------------
   Block one: the grant card */

.rk-grant-card { margin-top: 20px; }

/* The day it was given, at the card step. It is the heading of the card and
   deliberately not an h2: the screen's heading list is the page title and the
   three section eyebrows, and a date is not a section. */
.rk-grant-when {
  font-size: var(--t-card);
  font-weight: 500;
  line-height: var(--lh-head);
  letter-spacing: var(--ls-head);
  color: var(--text-primary);
}

/* What the grant is, in one paragraph. 52ch is the design's measure here, and it
   is narrower than the 62ch the rows below use because this one sits BESIDE the
   mark rather than across the card. */
.rk-grant-receipt {
  margin-top: 6px;
  max-width: 52ch;
  text-wrap: pretty;
}

.rk-grant-who { margin-top: 8px; }


/* --------------------------------------------------------------------------
   Blocks two and three: the two row cards

   One construction, used twice: an uppercase header row, then rows separated by
   a hairline. `overflow: hidden` so the first row's top rule cannot escape the
   card's radius. */

.rk-grant-rows {
  margin-top: 16px;
  overflow: hidden;
}

.rk-grant-head { padding: 14px 18px; }

.rk-grant-row {
  padding: 15px 18px;
  border-top: 1px solid var(--border);
}

.rk-grant-row-title { color: var(--text-primary); }

.rk-grant-row-body {
  margin-top: 5px;
  line-height: 1.6;
  max-width: 62ch;
  text-wrap: pretty;
}

/* The title and the board's status pill on one baseline, wrapping rather than
   squeezing: a status is four words at its longest and a title can be twelve, so
   on a phone the pill drops under the title instead of shrinking it. */
.rk-grant-asked-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 14px;
  flex-wrap: wrap;
}

/* It is a link, so it takes the accent on hover like every other link, and it
   reads at the row title's colour until then: the row is a fact about what was
   asked, not a call to action. */
.rk-grant-asked-title { color: var(--text-primary); }
.rk-grant-asked-title:hover { color: var(--accent); }


/* --------------------------------------------------------------------------
   Block four: the closing line */

.rk-grant-close {
  margin-top: 16px;
  max-width: 62ch;
  text-wrap: pretty;
}


/* --------------------------------------------------------------------------
   21. PULL DOWN TO REFRESH

   The gesture js/pull-refresh.js draws, ported from css/app.css and tokenised
   the way chip C16 ported the consent bar, and for the same reason: the app
   moved onto this stylesheet and the rules did not come with it.

   IT IS THE HOME SCREEN APP'S ONLY RELOAD. Saved to a phone, the site runs
   standalone: no address bar, no reload button, and iOS does not put the pull
   gesture back. Without this an athlete looking at yesterday's Today has
   nothing to pull and nothing to tap, and force quitting the app is the only
   way to ask for today's. In a browser tab the module attaches nothing, so
   none of these rules ever apply there and Safari's own gesture stands.

   WHAT IT LOOKS LIKE IS THE PLATFORM'S, DELIBERATELY. A spoke spinner in the
   strip that opens above the page, spokes lighting one at a time as the finger
   comes down. An athlete who uses the site in a browser on one device and as an
   app on another should not have to learn two gestures, so this site does not
   invent an idiom for something every phone already agrees on.

   Z-INDEX 20 IS UNDER BOTH OVERLAYS, and it means something different here
   from what it meant in css/app.css. There the top bar was fixed at 40 and
   closed over the strip; on this file `.rk-top` is in normal flow and rides
   down with the page, so what 20 buys is the consent bar at 30 and `.rk-modal`
   at 40 staying above a spinner that is only ever visible mid gesture.

   The seat's transform is written per frame by the script and only the resting
   state is here, which is why there is no `transform` in the seat's own rule:
   an identity transform left on an ancestor makes it the containing block for
   every fixed descendant, which is the failure the script is careful about and
   the stylesheet must not reintroduce. */

.pull-refresh {
  position: fixed;
  z-index: 20;
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  justify-content: center;
  pointer-events: none;
}

/* Half the spinner's height, so translating the seat by half the open strip
   centres the spinner in it. */
.pull-refresh-seat {
  margin-top: -13px;
  opacity: 0;
}

.pull-refresh-spinner {
  display: block;
  width: 26px;
  height: 26px;
}

.pull-refresh-spinner line {
  stroke: var(--text-muted);
  stroke-width: 2.4;
  stroke-linecap: round;
  opacity: 0;
}

/* Settling back after a pull that was let go short of the threshold, and the
   ride down into place on one that was not. Absent while the finger is down, so
   the page tracks it exactly. Kept in step with SETTLE_MS in the script. */
.pull-refresh-settling .pull-refresh-seat {
  transition: transform 0.22s ease-out, opacity 0.22s ease-out;
}

/* Twelve steps rather than a smooth turn, because the spokes are twelve and a
   continuous rotation between them reads as a wobble rather than a chase. */
.pull-refresh-spinning .pull-refresh-spinner {
  animation: pull-spin 0.9s steps(12, end) infinite;
  transform-origin: 50% 50%;
}

@keyframes pull-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .pull-refresh-spinning .pull-refresh-spinner { animation: none; }
}



/* ==========================================================================
   22. THE 404 PAGE
   ==========================================================================

   FM-52 chip C26, appended below everything before it for the reason every
   section since C4 gives: more than one chip is in this file at a time, so a
   rebase should be an append rather than a merge. Nothing above this line
   moved, and this section declares no token of its own.

   THE PAGE IS NOT A FILE UNDER website/. It is rendered by
   scripts/notfound.py, because a 404 answers at every address the server does
   not otherwise answer and so has no address of its own. That header carries the
   whole of the reasoning. What it means HERE is that the usual protection does
   not apply: content_test.py reads every rk- class the PAGES use and holds it to
   a rule declared somewhere in this file, and the 404's markup is not in that
   walk. notfound_test.py does the same walk over the rendered bodies instead, so
   a class written there and not declared here is still a red build.

   THREE TREATMENTS, AND ONLY THE FIRST NEEDS A BLOCK OF ITS OWN. The signed-out
   variant sits on `.rk-signin`, which is section 16's full-bleed graphite,
   because Design specified the sign-in shell by name. The public variant is the
   ordinary public page shell from sections 8, 9 and 11. So what is below is the
   signed-in column, the two link-row shapes, and the handful of places the other
   two need a margin the shell they borrowed does not supply.

   EVERY COLOUR IS A TOKEN, and the signed-out literals in the handoff are the
   reason that is worth saying. It names `#b6b2a7` secondary, `#8b867a` muted and
   `#e07a4f` for the link, where `--text-secondary`, `--text-muted` and
   `--oxide-lift` in dark theme are `#c3bfb4`, `#9b978b` and `#c95f36`. Section
   16 met exactly this and made the same call: the literals are the delivered
   design's own ramp for a page outside the themed container, this file has that
   ramp already, and a second set of near-identical greys is a second thing to
   keep in step. The tokens are the lighter of the two pairs in each case, so
   nothing lost contrast by the substitution. */

/* The signed-in column. 640px and the design's own padding, which is narrower
   than any `data-col` step and deliberately so: this screen holds one sentence,
   two buttons and four rows, and the 760px article measure would leave the
   heading floating. Written as its own class rather than as a seventh
   `.rk-col[data-col]` because that class belongs to section 2 and a new step
   there is a step every screen gets. */
.rk-nf {
  max-width: 640px;
  margin-inline: auto;
  padding: 96px var(--gutter) 140px;
}

.rk-nf-mark { display: block; }

.rk-nf-eyebrow { margin-top: 22px; }

.rk-nf-title { margin-top: 8px; }

.rk-nf-lede {
  margin-top: 12px;
  max-width: 52ch;
  text-wrap: pretty;
}

/* The requested address, echoed back. A well rather than a card: it is a quoted
   fact about the request and not a thing to act on, so it is inset rather than
   raised, and it is never a link.

   ONE LINE, ALWAYS. `white-space: nowrap` with the ellipsis is what holds it
   there, and scripts/notfound.py caps the string at 120 characters before it
   ever reaches this rule, because a 300 character path is a layout problem
   before it is a text overflow one. Both halves, because either alone leaves a
   case: the cap alone still overflows a narrow phone, and the ellipsis alone
   lets an arbitrarily long string sit in the DOM. */
.rk-nf-echo {
  margin-top: 20px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--surface-1);
  font-family: var(--font-mono);
  font-size: var(--t-small);
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rk-nf-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  margin-top: 26px;
}

/* The icon and the label inside the primary button. `.rk-btn` is an inline-flex
   box with no gap of its own, because most buttons on this site are a word. */
.rk-nf-go { gap: 9px; }

/* THE WAY BACK, AND THE ONE RULE HERE THAT IS NOT ABOUT LOOKS.

   The server ships that button with the `hidden` attribute and js/notfound.js
   removes it only for a same-origin referrer, because a control that goes
   nowhere is worse than no control at all on a page that is already telling
   somebody a link was broken.

   `hidden` DOES NOT WORK ON ITS OWN HERE, and that is the whole reason this
   rule exists. The browser's own sheet says `[hidden] { display: none }`, and
   `.rk-btn` above says `display: inline-flex`; a class beats the user agent, so
   the button rendered on every cold entry and the attribute did nothing. Every
   test in the suite passed while it did, because none of them reads a computed
   style: the markup was right, the module was right, and the page was wrong.
   Somebody opening it in a browser is what found it, which is the same way C16
   was found and is an argument for looking rather than for another checker.

   Scoped to this button rather than written as `.rk-btn[hidden]`, because
   `.rk-btn` belongs to section 4 and content_test.py refuses a section heading
   a rule with a class an earlier one owns. A `.rk-btn` anywhere else on the
   site that wants to hide itself needs the same line, in its own section. */
.rk-nf-back[hidden] { display: none; }

/* The divider above the link list, and the gap under the eyebrow that names it.
   Shared by all three variants, which is why it is not part of `.rk-nf`. */
.rk-nf-more {
  margin-top: 34px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}

.rk-nf-rows {
  display: grid;
  gap: 2px;
  margin-top: 10px;
}

/* One row. The negative left margin is the design's and is load-bearing rather
   than decorative: without it the hover fill starts exactly at the text and
   reads as a highlight rather than as a row, and the icon column looks indented
   against everything above it. */
.rk-nf-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  min-height: 44px;
  margin-left: -12px;
  padding: 11px 12px;
  border-radius: var(--r);
  color: var(--text-primary);
  transition: background var(--motion);
}

.rk-nf-row:hover {
  background: var(--surface-1);
  text-decoration: none;
}

.rk-nf-row > i {
  flex: none;
  width: var(--i-lg);
  margin-top: 2px;
  font-size: var(--i-lg);
  color: var(--text-muted);
}

/* A two-line row: what it is, and where it goes. The path is drawn because
   these five are public addresses a reader can decide to trust before they
   click, which on a page that has just told somebody a link was wrong is worth
   the line. `min-width: 0` is what lets the path ellipsis instead of pushing the
   row wider than the column. */
.rk-nf-rowtext { min-width: 0; }

.rk-nf-rowlabel {
  display: block;
  line-height: 1.35;
  text-wrap: pretty;
}

.rk-nf-rowpath {
  display: block;
  margin-top: 3px;
  overflow: hidden;
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  color: var(--text-muted);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rk-nf-close {
  margin-top: 24px;
  max-width: 58ch;
  text-wrap: pretty;
}

/* ---- the signed-out variant, on section 16's ground ---------------------- */

/* 440px rather than `.rk-signin-col`'s 400px, which is the one measurement this
   variant does not share with the sign-in screen: that screen is a card holding
   a single field, and this one is a heading block over five two-line rows. */
.rk-nf-out {
  max-width: 440px;
  margin-inline: auto;
}

.rk-nf-outhead {
  margin-top: 40px;
  text-align: center;
}

/* `--t-card` rather than the `--t-page` the signed-in heading takes. The same
   sentence, set smaller, because here it is centred in a 440px column with
   nothing else on the screen and the page step reads as shouting. Design draws
   both and this is the difference between them. */
.rk-nf-outtitle {
  margin-top: 8px;
  font-size: var(--t-card);
  letter-spacing: -.015em;
}

.rk-nf-outlede {
  margin-top: 12px;
  color: var(--text-secondary);
  text-wrap: pretty;
}

/* Full width, chalk on iron. `.rk-signin-go` supplies the colour, which is the
   same brand pair the Sign in button next door draws, so this adds the top
   margin and nothing else. */
.rk-nf-signin { width: 100%; margin-top: 28px; }

.rk-nf-outsub {
  margin-top: 16px;
  font-size: var(--t-small);
  color: var(--text-muted);
  text-align: center;
}

.rk-nf-outclose {
  margin-top: 22px;
  font-size: var(--t-small);
  color: var(--text-muted);
  text-wrap: pretty;
}

/* The one link in the closing line. Oxide rather than the blue action accent,
   because it is an email address in a sentence rather than a thing to press,
   and this is the one screen where the accent would be the fourth blue on a
   page with no other action on it. */
.rk-nf-outclose a { color: var(--oxide-lift); }

/* ---- the public variant, in the ordinary page shell ---------------------- */

/* The sign-in call and the line under it, side by side where there is room and
   stacked where there is not. The public treatment is an article rather than a
   screen, so the button sits in the flow instead of being the page. */
.rk-nf-pubact {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  margin-top: 26px;
}

@media (min-width: 700px) {
  .rk-nf { padding-top: 110px; }
}


/* ==========================================================================
   23. THE PUBLIC PALETTE
   ==========================================================================

   The design bundle of 2 August 2026 splits the two sides of the product apart
   for the first time. Section 5 of its README says it in one line: "The app and
   the public site do not share an accent." The signed-in app keeps the calm
   blue that was settled on 2026-07-30 and is not reopened here. The public site
   is pinned graphite with a warm oxide accent and a chalk-on-iron primary
   button, and the README names building a public page with the blue accent as
   "the error to watch for". It was the error: every public page resolved
   `--accent` to the app's `#85b7eb`.

   IT IS AN OVERRIDE RATHER THAN A SECOND TOKEN SET, and that is the whole point
   of doing it here rather than in a `public.css`. The bundle's public table
   names its colours `--ground`, `--band`, `--card`, `--edge`, `--ink`, `--ease`
   and `--flag`, and three of those already exist under other names with
   identical values: `--ground` IS `--surface-0` dark, `--band` IS `--surface-1`
   dark, `--edge` IS `--border` dark. Declaring the seven again would give every
   public rule two names for one colour and a second thing to keep in step. So
   what is written below is only what actually DIFFERS, mapped onto the names
   the rest of this file already draws with, and every existing `.rk-` rule
   keeps working and simply comes out warm.

   The mapping, so the bundle's table can be read against this block:

     --ground        --surface-0   #17150f   already the dark value
     --band          --surface-1   #201d16   already the dark value
     --card          --surface-2   #241f18   MOVED, dark is #2a271f
     --edge          --border                already the dark value
     --edge-strong   --border-strong         already the dark value
     --ink / -2 / -3 --text-primary / -secondary / -muted   already the values
     --accent        --accent      see below
     --accent-bg     --accent-bg   rgba(201,95,54,.18)
     --btn-bg/-fg    --btn-bg/-fg  #d9cfbc on #33291f, which is chalk on iron
     --ease          --success     #7cc9cf
     --flag          --warning     #ef9f5a
     --alert         --alert                 already the value

   `--surface-3` has no entry in the public table at all, so it keeps the app's
   dark value. That matters below.

   THE ACCENT IS `#ec9068` AND NOT THE DELIVERED `#e07a4f`, for the reason
   `--text-muted` and `--record` moved in section 1 and were confirmed back.
   This system asks a semantic colour to clear AA on all four surfaces AND on
   its own tint over each of them, which is more grounds than the bundle's own
   prototype ever draws it on. `#e07a4f` over `rgba(201,95,54,.18)` is 4.45 on
   a card and 3.74 on a well, both failures. `#ec9068` is the same hue lifted
   until the worst of the four clears, at 4.63, and it leaves the delivered
   `--accent-strong` `#f0996f` sitting above it as the hover exactly as drawn.
   foundations_test.py computes all of this rather than trusting this comment.

   WHY `:root[data-public]` AND WHY IT IS LAST IN THE FILE. The two dark blocks
   are `:root:not([data-theme="light"])` and `[data-theme="dark"]`, so an
   override has to match their specificity and come after them or a public page
   would go blue again the moment the reader's device asked for dark. A bare
   `[data-public]` is one class short and would lose that race silently. */

:root[data-public] {
  --surface-2: #241f18;

  --accent: #ec9068;
  --accent-bg: rgba(201, 95, 54, .18);

  /* Chalk on iron: `--chalk` and `--iron` from section 1, so the primary button
     on a public page is the identity rather than a third blue. Written as the
     literal values rather than as `var()`, because foundations_test.py reads
     these declarations out of the text and computes the contrast itself, and a
     reference it cannot resolve is a pair it cannot grade. 9.20. */
  --btn-bg: #d9cfbc;
  --btn-fg: #33291f;

  --success: #7cc9cf;
  --warning: #ef9f5a;
}


/* ==========================================================================
   24. THE ACCOUNT MENU AS NAVIGATION
   ==========================================================================

   Section 8 built this menu as one flat list under the theme control. The
   design draws three labelled blocks, and the first of them is "Go to": the
   same six screens the bar carries, in the menu, at every width. That block was
   never built, which on a phone meant the six screens had exactly one route
   into them, the bar's own row, and that row is six entries laid out in the
   ~170px left between the wordmark and the account chip. One and a half tabs
   visible, four and a half behind a sideways scroll. Nothing was broken in a
   way a test could see: every screen still answered at its own address.

   THE BAR'S ROW IS NOW DRAWN FROM 700px UP AND NOT BELOW, and that is a
   deliberate resolution of the bundle disagreeing with itself rather than a
   preference. `Reckoner Redesign.dc.html` renders no nav row under 1024 and
   puts the six in this menu; `decisions/ANSWERS-fm52-cutover.md` G16 rules
   "drop the bottom tab bar, keep the scrolling nav". Both are satisfied by
   keeping the row where it can scroll to something and letting the menu carry
   the six where it cannot. Jason's call, 2026-08-02.

   `:root:not([data-public])` because `.rk-nav` is two components wearing one
   class. The public bar's three links are not six and have their own two-row
   treatment in section 7, so hiding `.rk-nav` outright would take the public
   nav down with it. The `:not()` also buys the specificity: (0,2,1) sits above
   both `.rk-nav` and `[data-public] .rk-nav`, so this wins wherever it is
   written rather than only if it is written last. */

:root:not([data-public]) .rk-nav { display: none; }

/* The nav row was also the thing holding the account chip against the right
   edge: it is the flex item that grew, and with it gone the chip slid back to
   sit against the wordmark. That is not just untidy. The menu hangs off the
   chip with `inset-inline-end: 0`, so a chip 145px short of the edge is a
   300px menu starting at -55px, with its first character off the screen. */
:root:not([data-public]) .rk-account { margin-inline-start: auto; }

@media (min-width: 700px) {
  :root:not([data-public]) .rk-nav { display: flex; }
  :root:not([data-public]) .rk-account { margin-inline-start: 0; }
}

/* Six more rows means the menu can outgrow a phone, so it scrolls inside
   itself rather than off the bottom of the viewport. 78vh is the design's
   number and it leaves the bar it hangs from visible above it. */
.rk-menu {
  max-height: 78vh;
  overflow-y: auto;
}

/* The label over each block. Same eyebrow treatment section 2 gives a screen,
   at menu scale: a heading rather than a divider, because what separates Go to
   from Your account is what they are for and not a hairline. */
.rk-menu-sec {
  padding: 12px 12px 6px;
  color: var(--text-muted);
  font-size: var(--t-micro);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
}

/* The plate's byline is one nowrap line everywhere else it appears, which is
   right on a board row and wrong in a 300px menu: "jpcams" plus the mark plus
   "Founding member 01" runs past the edge and the number is cut off. The design
   stacks them, the name on its own line and the grant under it, so that is what
   the menu asks for. Scoped here rather than changed in section 8, because the
   nowrap is load-bearing on every other surface the byline is drawn on. */
.rk-menu-who .rk-byline { display: flex; flex-wrap: wrap; white-space: normal; }
.rk-menu-who .rk-byline-name { flex-basis: 100%; }

/* Section 8 closed the theme row with a hairline, because it was the thing
   separating the plate above from the list below. The three headings do that
   job now, and the rule would land between the buttons and the line explaining
   them. */
.rk-menu-theme { border-bottom: 0; padding-bottom: 0; }

/* The one thing about the three theme buttons that the words on them do not
   say. */
.rk-menu-note {
  padding: 8px 12px 4px;
  color: var(--text-muted);
  font-size: var(--t-micro);
}

/* The screen you are on. Section 8 coloured the label and left the row itself
   flat, which reads as a link that happens to be orange rather than as your
   position in a list of ten. The design fills it and marks the leading edge,
   the same inset rule the bar draws under its active tab turned on its side. */
.rk-menu-item[aria-current="page"] {
  background: var(--accent-bg);
  box-shadow: inset 2px 0 0 var(--accent);
}

/* Leaving is the one destructive thing in the menu and the design draws it as
   one. `--alert` rather than `--text-secondary`, which is what section 8 gave
   it and which made Sign out the quietest row on a list of ten. */
.rk-menu-out { color: var(--alert); }
.rk-menu-out i { color: var(--alert); }
.rk-menu-out:hover { background: var(--alert-bg); }
