/* IronBar app stylesheet — the "Kinetic Dark" design system.
   Cached and served at /static/app.css (cache-busted by ?v=<hash>).

   The look is dark-first: tiered charcoal surfaces, no drop shadows, and a
   single high-frequency lime reserved for one job — marking what you can act
   on and what you have already done. Depth comes from tone and a hairline
   inner glow; the only cast light in the app is the lime one under a primary
   button. Type carries the energy: Anybody for display, Hanken Grotesk for
   prose, and JetBrains Mono in caps for every label, so a measurement never
   looks like a sentence.

   Layout:
   0. Fonts               1. Tokens + theme
   2. Global + shared components
   3. Per-page blocks (.p-<name> …)   4. Utilities (.u-…) */

/* ============ 0. FONTS ============
   Self-hosted: nothing is fetched from a third party, and the faces are
   variable fonts instanced down to their weight axis alone (Anybody ships a
   width axis we never vary, which was more than half its bytes). Split by
   unicode-range so a Latin reader never downloads a Cyrillic cut — English
   costs ~88KB, once, then it is cached.

   Anybody and Hanken Grotesk have no basic Cyrillic (U+0400–045F), so ru/uk
   headings and prose fall through to the next family in each stack. That is
   why the stacks below are spelled out in full instead of ending at a bare
   `sans-serif`: the fallback is a deliberate face, not whatever is left. */
@font-face {
  font-family: 'Anybody';
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url('/static/fonts/anybody-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Anybody';
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url('/static/fonts/anybody-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('/static/fonts/hanken-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('/static/fonts/hanken-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('/static/fonts/hanken-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/static/fonts/jetbrains-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/static/fonts/jetbrains-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/static/fonts/jetbrains-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/static/fonts/jetbrains-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}

/* ============ 1. TOKENS + THEME ============
   Dark is the default and the design's home; light is a faithful inversion
   for members who ask for it, not a second identity. --brand is the lime,
   overridable per gym from base.html — a tenant that sets its own colour gets
   the same system wearing it, because every accent below is derived from
   --brand rather than repeating a hex. */
:root {
  /* ---- Type. Three families, three jobs. ---- */
  --font-display:'Anybody', ui-rounded, 'SF Pro Rounded', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --font-body:'Hanken Grotesk', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
  --font-mono:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* ---- Dark surfaces (default). Five tiers do the work drop shadows used
     to: lowest is the furniture a page sits under, --bright is the top of
     the stack. Nothing here is pure black — #131313 keeps a technical edge
     without crushing the charcoals above it. ---- */
  --bg:#131313; --panel:#1c1b1b; --panel-2:#201f1f;
  --panel-3:#2a2a2a; --panel-low:#0e0e0e; --bright:#393939;
  --text:#e5e2e1; --muted:#c4c9ac; --border:#444933; --outline:#8e9379;

  /* ---- The one accent. Lime means "you can act on this" or "this is
     done" — the mockups deliberately collapse those two, and so do we: in a
     gym app the thing you just finished and the thing you tap next are the
     same colour of energy. Everything that is neither stays charcoal. ---- */
  --brand:#c3f400;
  --brand-ink:var(--brand);                                       /* lime as text, on dark */
  --brand-fill:var(--brand);                                      /* lime as a surface */
  /* Near-black olive, not the mockups' #556d00: that fails contrast on the
     lime fill at label size, and every CTA in the app is label-sized. */
  --on-brand:#161e00;
  --brand-soft:color-mix(in srgb, var(--brand) 12%, transparent);
  --action:var(--brand-fill); --on-action:var(--on-brand);

  /* Semantic signals. Under Kinetic, "done" IS the accent — the mockups
     deliberately collapse the two. Rose separates them again (it has a green
     of its own), which is why every "done" surface reads --go/--go-ink/
     --on-go rather than the brand directly: one look can fuse them, the other
     can split them, and no component has to know which. */
  --go:var(--brand); --go-ink:var(--brand-ink); --on-go:var(--on-brand);
  --go-soft:color-mix(in srgb, var(--go) 10%, transparent);
  --wait:#fbbe4b; --wait-soft:#372a12; --wait-ink:#fbbe4b;
  --info:#adc6ff; --info-soft:#00285c;
  --error:#ffb4ab;

  /* The face a control's label is set in. Kinetic shouts in mono caps;
     Rose swaps it for the display face, which is its whole point. */
  --font-control:var(--font-mono);
  --control-tracking:.1em;

  /* ---- Depth. No drop shadows: a hairline of light along the top edge is
     what separates a card from the canvas, and the only cast light in the
     app is the lime under a primary button. ---- */
  --shadow:inset 0 1px 0 0 rgba(255,255,255,.06);
  --glow:0 4px 20px color-mix(in srgb, var(--brand) 25%, transparent);
  /* The only gradient in the system, and it is one gradient rather than six so
     that it still means something: a top-down sheen over a filled control, the
     way light falls on a machined face. Pure white at 15% and out by the
     bottom edge, so it survives every accent without being tuned per look and
     costs nothing in contrast — the ink underneath is unchanged. */
  --sheen:linear-gradient(180deg, rgba(255,255,255,.15) 0%, rgba(255,255,255,0) 100%);
  --glow-soft:0 0 15px color-mix(in srgb, var(--brand) 18%, transparent);

  /* ---- Shape + rhythm, on the 8px base. Every component reads these;
     nothing hard-codes a radius, a control height or a step of spacing.
     Pills are reserved for things you tap — that is what makes them read
     as tappable. ---- */
  --r:1rem;          /* panels and cards */
  --r-lg:1.5rem;     /* the big hero surfaces */
  --r-md:.75rem;     /* tiles, chips, media */
  --r-sm:.5rem;      /* form controls, small buttons */
  --r-pill:999px;
  --control-h:3rem;  /* every input, select and small button is this tall */
  --sp-1:.25rem; --sp-2:.5rem; --sp-3:.75rem; --sp-4:1rem; --sp-5:1.5rem; --sp-6:2.5rem;
  /* The chevron a select wears — ours, because Safari's own is unstyleable. */
  --chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23c4c9ac' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  /* Not `dark light`: the browser would then paint its own widgets from the
     device preference while the page stayed dark. */
  color-scheme:dark;
}

/* Light: the same system, inverted. The lime survives as a fill but cannot
   be text on white, so --brand-ink darkens to an olive that can.

   Deliberately NOT wired to `prefers-color-scheme`. That query reports
   `light` on every device that has not explicitly opted into dark, so
   honouring it would have shipped the light ground to most members and made
   the dark identity the exception. Dark is what this design *is*; light is
   something a member asks for, and then it sticks. */
:root[data-theme="light"] {
  --bg:#f4f4f0; --panel:#ffffff; --panel-2:#ecece6;
  --panel-3:#e2e2da; --panel-low:#f0f0eb; --bright:#d8d8ce;
  --text:#131313; --muted:#5c6047; --border:#d5d9c4; --outline:#8e9379;
  --brand-ink:#4a6b00;
  --wait-soft:#fdf1d8; --wait-ink:#7a5300;
  --info:#2a5db0; --info-soft:#e2eaf9;
  --error:#b42318;
  --shadow:inset 0 1px 0 0 rgba(255,255,255,.7), 0 1px 2px rgba(19,19,19,.05);
  --chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%235c6047' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  color-scheme:light;
}
/* Re-asserts the dark defaults when a member switches back from light. */
:root[data-theme="dark"] {
  --bg:#131313; --panel:#1c1b1b; --panel-2:#201f1f;
  --panel-3:#2a2a2a; --panel-low:#0e0e0e; --bright:#393939;
  --text:#e5e2e1; --muted:#c4c9ac; --border:#444933; --outline:#8e9379;
  --brand-ink:var(--brand);
  --wait-soft:#372a12; --wait-ink:#fbbe4b;
  --info:#adc6ff; --info-soft:#00285c;
  --error:#ffb4ab;
  --shadow:inset 0 1px 0 0 rgba(255,255,255,.06);
  --chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23c4c9ac' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  color-scheme:dark;
}

/* ---- The second look: "Kinetic Rose" ----
   Same bones, different temperament. Where Kinetic is a cockpit — charcoal,
   lime, glowing — Rose is editorial: high-chroma pink on near-white, no glow
   anywhere, and a shape language that is strictly square. That squareness is
   the whole identity, so it is expressed the only place it needs to be: the
   radius tokens. Every component already reads them, which is why switching
   look re-cuts every corner in the app without touching a component rule.

   Rose also splits "done" back out from "act on this" (a deep green does the
   former), so the two states stop sharing a colour the way they do under
   Kinetic. Selected by data-look on <html>; light is Rose's home ground, and
   it carries a dark variant for the same gym-at-night reason. */
:root[data-look="rose"] {
  /* Sharp (0px) is Rose's *default* corner, not a fixture of it — the
     data-corners block below can pin either shape onto either look. "Every
     element — buttons, input fields, cards and images — should have square
     corners." Avatars stay circular whatever is chosen: they set 50%
     directly rather than reading a token. */
  --r:0; --r-lg:0; --r-md:0; --r-sm:0; --r-pill:0;

  /* Labels and buttons move to the display face — Rose's typographic
     signature is Anybody in caps, not a monospace readout. */
  --font-control:var(--font-display);
  --control-tracking:.06em;

  --brand:#e51245;
  --brand-fill:var(--brand);
  --on-brand:#ffffff;
  --brand-soft:color-mix(in srgb, var(--brand) 12%, transparent);

  /* No neon: Rose builds depth from borders, so the glows collapse to
     nothing rather than being restated per component. */
  --glow:none;
  --glow-soft:none;

  /* Dark is Rose's night mode: charcoal surfaces, rose-tinted edges, and the
     accent lifted to a tint that can be read as text on black. */
  --bg:#131313; --panel:#1c1b1b; --panel-2:#211d1e;
  --panel-3:#2c2627; --panel-low:#0e0e0e; --bright:#3b3335;
  --text:#f0eded; --muted:#d6b8b9; --border:#5d3f40; --outline:#916e6f;
  --brand-ink:#ffb3b5;
  --go:#65dcab; --go-ink:#65dcab; --on-go:#00281a;
  --wait:#fbbe4b; --wait-soft:#3a2b12; --wait-ink:#fbbe4b;
  --info:#ffc1c6; --info-soft:#5c0022;
  --error:#ffb4ab;
  --shadow:inset 0 1px 0 0 rgba(255,255,255,.06);
  --chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23d6b8b9' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  color-scheme:dark;
}
/* Rose on paper — the palette the look was drawn in. Off-white rather than
   white, "cleaner and more premium … while maintaining high contrast". */
:root[data-look="rose"][data-theme="light"] {
  --bg:#f9f9f9; --panel:#ffffff; --panel-2:#f3f3f3;
  --panel-3:#e8e8e8; --panel-low:#f6f6f6; --bright:#e2e2e2;
  --text:#1a1c1c; --muted:#5d3f40; --border:#e6bcbd; --outline:#916e6f;
  --brand-ink:#ba0034;
  --go:#00694b; --go-ink:#00694b; --on-go:#ffffff;
  --wait:#b07300; --wait-soft:#fdf1d8; --wait-ink:#7a5300;
  --info:#b4003d; --info-soft:#ffe8e8;
  --error:#ba1a1a;
  --shadow:inset 0 1px 0 0 rgba(255,255,255,.7), 0 1px 2px rgba(26,28,28,.05);
  --chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%235d3f40' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  color-scheme:light;
}
/* ---- Corner shape, as its own axis ----
   Each look ships with an opinion — Kinetic is rounded and reaches for a pill
   on anything you tap, Rose is strictly square — but shape and palette are
   separable, so a member can have rose corners on a lime app or vice versa.
   Set data-corners on <html> to pin one; leave it off to get whatever the
   current look prefers.

   These come *after* the look blocks on purpose: same specificity (0,2,0),
   so source order is what lets an explicit choice beat the look's default. */
:root[data-corners="rounded"] {
  --r:1rem; --r-lg:1.5rem; --r-md:.75rem; --r-sm:.5rem; --r-pill:999px;
}
/* "Sharp", not "square". A true 0 read as unfinished rather than deliberate —
   every panel, field and button met at a hard right angle, and on a dark ground
   the corners looked clipped instead of drawn. A 3px radius is still crisp at a
   glance and still obviously the sharp option next to the 16px one; it just
   stops the edges from looking like a rendering fault. Pills keep a small
   radius for the same reason, rather than becoming rectangles. */
:root[data-corners="sharp"] {
  --r:3px; --r-lg:4px; --r-md:3px; --r-sm:2px; --r-pill:4px;
}

/* The one place a square corner would be wrong: anything that is a portrait
   or a dot stays round in both looks. */
:root[data-look="rose"] .lrow .thumb,
:root[data-look="rose"] .iconbtn,
:root[data-look="rose"] .lrow .go-ic { border-radius:50%; }
/* Rose defines depth as "1px solid, low-contrast" — a secondary button is a
   2px rose rule, not a hairline. */
:root[data-look="rose"] .btn-ghost,
:root[data-look="rose"] .ghost,
:root[data-look="rose"] .btn-secondary { border-width:2px; border-color:var(--brand-ink); color:var(--brand-ink); }

/* ============ 2. GLOBAL + SHARED COMPONENTS ============ */
* { box-sizing: border-box; }
/* The canvas is painted on <html>, not left to propagate up from <body>.
   Two reasons: iOS rubber-band scrolling exposes the canvas, and a white
   flash under a dark app is exactly what you notice; and once body's
   background propagates, WebKit reports body's own as transparent, which
   makes the thing untestable. */
html { -webkit-text-size-adjust: 100%; background: var(--bg); }
/* The `hidden` attribute must win over component display rules (e.g. the
   grid steppers) so JS-toggled input groups actually hide. */
[hidden] { display: none !important; }
body {
  margin: 0;
  font-family: var(--font-body);
  font-size: 16px;                 /* body-md: 16/24 */
  background: var(--bg);
  color: var(--text);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
/* Measurements are mono and tabular so a column of them lines up and a
   changing timer doesn't jitter. Anything the user reads as a *number*
   wears this, not the prose face. */
.num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

/* ---- The three type roles the design system names ----
   .eyebrow  the mono-caps label above everything: a section name, a unit,
             a status. Small, wide-tracked, never sentence case.
   .stat     the measurement itself, sized to be read across a gym floor.
   .display  a page or hero title. */
.eyebrow {
  font-family: var(--font-mono);
  font-size: .75rem; line-height: 1rem; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted);
}
.eyebrow.on { color: var(--brand-ink); }
.stat {
  font-family: var(--font-display);
  font-size: 2rem; line-height: 1; font-weight: 800;
  letter-spacing: -.01em; font-variant-numeric: tabular-nums;
  color: var(--text);
}
/* The unit trailing a measurement — smaller and quieter, so the figure
   keeps the weight. */
.stat small, .stat .u { font-size: .5em; font-weight: 700; color: var(--muted); margin-left: .15em; }
.display { font-family: var(--font-display); font-size: 2.25rem; line-height: 1.16; font-weight: 800; letter-spacing: -.02em; }

/* ---- Header ----
   Blurred and translucent so the content stays visible sliding underneath —
   the design system's rule for anything that floats over the canvas. */
header.topbar {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  padding-top: calc(var(--sp-3) + env(safe-area-inset-top));
  border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
  background: color-mix(in srgb, var(--bg) 80%, transparent);
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
}
.brand-link { display: inline-flex; align-items: center; }
.brand-logo { height: 34px; width: auto; max-width: 56vw; object-fit: contain; display: block; }
/* Dark is the default, so the dark-ground logo is what shows unless the
   member has asked for light. */
.logo-light { display: none; }
:root[data-theme="light"] .logo-light { display: block; }
:root[data-theme="light"] .logo-dark { display: none; }
:root[data-theme="dark"] .logo-light { display: none; }
:root[data-theme="dark"] .logo-dark { display: block; }
.topbar-right { display: flex; align-items: center; gap: var(--sp-2); margin-left: auto; }
/* Round, quiet, and 44px whatever the icon inside it — the mockups' header
   affordance. */
.iconbtn {
  width: 44px; height: 44px; margin: 0; padding: 0;
  border: 1px solid color-mix(in srgb, var(--border) 60%, transparent); border-radius: 50%;
  background: transparent; color: var(--text);
  cursor: pointer; display: grid; place-items: center; line-height: 1;
  transition: background-color .12s ease, color .12s ease;
}
.iconbtn svg { width: 22px; height: 22px; }
.iconbtn:hover { background: var(--panel-3); color: var(--brand-ink); }

/* ---- "More" overflow menu (secondary destinations) ---- */
.moremenu {
  display: none;
  /* Opens upward, above the bottom tab bar. */
  position: fixed; bottom: calc(4.6rem + env(safe-area-inset-bottom)); right: .6rem; left: auto;
  z-index: 60; min-width: 15rem;
  flex-direction: column; gap: 0;
  background: color-mix(in srgb, var(--panel-3) 95%, transparent);
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
  border: 1px solid var(--border); border-radius: var(--r);
  box-shadow: var(--shadow), 0 16px 40px -12px rgba(0,0,0,.6);
  padding: var(--sp-2);
}
.moremenu.open { display: flex; }
.moremenu a, .moremenu button {
  display: flex; align-items: center; gap: var(--sp-3); width: 100%;
  padding: .8rem; margin: 0; border: 0; min-height: 0; box-shadow: none;
  background: none; text-align: left; justify-content: flex-start; cursor: pointer;
  border-radius: var(--r-sm); text-decoration: none; color: var(--text);
  font-family: var(--font-body); font-weight: 600; font-size: 1rem;
  letter-spacing: normal; text-transform: none;
}
.moremenu a:hover, .moremenu button:hover { background: var(--bright); color: var(--text); transform: none; box-shadow: none; }
.moremenu a svg, .moremenu button svg { width: 22px; height: 22px; color: var(--muted); flex: none; }

/* ---- Layout ---- */
main {
  max-width: 40rem; margin: 0 auto;
  /* 16px side margins on mobile, per the design system's 4-column grid. */
  padding: calc(var(--sp-4) + env(safe-area-inset-top)) var(--sp-4) calc(5.6rem + env(safe-area-inset-bottom));
}
@media (min-width: 48rem) { main { padding-left: var(--sp-6); padding-right: var(--sp-6); } }
/* A phone column is the right measure on a phone and a stranded strip on a
   desktop — 40rem of content centred in 90rem of black reads as a screenshot
   pasted on a wall rather than an app. The column earns a little more width as
   the window grows, stopping well short of a full-bleed stretch: past ~56rem
   the line length stops being comfortable to read and the panels start looking
   like empty banners. */
@media (min-width: 64rem) {
  main { max-width: 48rem; }
  /* The bar floats clear of the bottom edge at this width, so it needs more
     room beneath the content than the docked version did — otherwise the last
     control on a page sits under it at the end of the scroll. */
  main { padding-bottom: calc(7.5rem + env(safe-area-inset-bottom)); }
}
@media (min-width: 90rem) { main { max-width: 56rem; } }

/* The dashboard is a stack of independent panels, which is the right shape on a
   phone and a waste of a desktop: widening the column only makes each panel a
   longer banner. Above 80rem they flow into two tracks instead, so the screen
   is used rather than merely filled.
   `align-items: start` matters — without it every panel in a row stretches to
   the tallest, and a one-line "Your plans" card grows to match the set logger
   beside it. The greeting and the banners span, because a page title in one
   column reads as a card. */
@media (min-width: 80rem) {
  .p-dashboard main {
    display: grid;
    grid-template-columns: 1fr 1fr;
    align-items: start;
    gap: var(--sp-4);
    max-width: 72rem;
  }
  .p-dashboard main > .greet,
  .p-dashboard main > .banner { grid-column: 1 / -1; }
  /* The grid owns the rhythm now; the panels' own bottom margin would double
     it into an uneven gutter. */
  .p-dashboard main > .panel { margin-bottom: 0; }
}
a { color: var(--brand-ink); }

/* ---- Shared primitives (kept for every template) ---- */
.panel {
  background: var(--panel);
  border: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
  border-radius: var(--r);
  padding: var(--sp-4);
  margin-bottom: var(--sp-3);
  box-shadow: var(--shadow);
}
/* One step up the tonal stack — a panel that needs to sit *on* another. */
.panel-2 { background: var(--panel-2); }
h1, h2, h3, .display { font-family: var(--font-display); }
h1 { margin: .1rem 0 var(--sp-3); font-size: 2.25rem; font-weight: 800; letter-spacing: -.02em; line-height: 1.16; text-wrap: balance; }
h2 { margin-top: 0; font-size: 1.5rem; font-weight: 700; letter-spacing: -.01em; line-height: 1.33; }
h3 { font-weight: 700; font-size: 1.125rem; letter-spacing: -.01em; }
/* A form label is a label: mono, caps, tracked — same role as an .eyebrow,
   so it looks like one. */
label {
  display: block; margin: var(--sp-3) 0 var(--sp-1);
  font-family: var(--font-mono);
  font-size: .75rem; line-height: 1rem; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted);
}
/* ---- Form controls: one shape, on every browser ----
   Safari and iOS paint their own chrome over a control the page hasn't fully
   claimed: a double chevron on a select, spinner arrows on a number field, a
   date field that sizes and centres itself. That is why the same form looked
   like three different forms. `appearance:none` takes it back, and everything
   below is drawn by us — one height, one radius, one border, everywhere. */
input, select, textarea {
  appearance: none; -webkit-appearance: none;
  width: 100%;
  min-height: var(--control-h);
  padding: .58rem .65rem;
  /* `font: inherit`, not a size: iOS otherwise shrinks control text to 11px
     and then zooms the page when it is focused. */
  font: inherit;
  line-height: 1.25;
  border-radius: var(--r-sm);
  border: 1px solid var(--border);
  /* A field is a well cut into the panel, not a card on top of it. */
  background: var(--panel-2);
  color: var(--text);
  transition: border-color .12s ease, box-shadow .12s ease;
}
select {
  padding-right: 2.2rem;
  background-image: var(--chevron);
  background-repeat: no-repeat;
  background-position: right .75rem center;
  background-size: 12px 8px;
}
/* The app has its own − / + steppers; the native ones were a second, smaller
   pair of arrows sitting next to them. */
input[type=number] { -moz-appearance: textfield; }
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
/* iOS centres a date's value and lets the field grow; hold it to the shape
   every other control has. */
input[type=date], input[type=time], input[type=datetime-local] { text-align: left; }
input[type=date]::-webkit-date-and-time-value,
input[type=time]::-webkit-date-and-time-value { text-align: left; margin: 0; }
input[type=date]::-webkit-calendar-picker-indicator { opacity: .5; }
input[type=search]::-webkit-search-decoration,
input[type=search]::-webkit-search-cancel-button { -webkit-appearance: none; }
textarea { min-height: 5.5rem; resize: vertical; }
/* Tick boxes stay the browser's own — they are accessible as they come, and
   only need to be told the brand colour. */
input[type=checkbox], input[type=radio] {
  appearance: auto; -webkit-appearance: auto;
  width: auto; min-height: 0; padding: 0; accent-color: var(--brand);
}
input[type=file] { appearance: auto; -webkit-appearance: auto; padding: .45rem; }
input::placeholder, textarea::placeholder { color: var(--muted); opacity: 1; }
input:disabled, select:disabled, textarea:disabled {
  opacity: .6; -webkit-text-fill-color: var(--muted);  /* iOS ignores `color` */
}
/* Focus lights the field rather than ringing it — the design system's
   "the entire field gains a glow when active". */
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: none; border-color: var(--brand);
  box-shadow: 0 0 0 1px var(--brand), var(--glow-soft);
}
/* Date/time fields carry an intrinsic minimum width from their native picker,
   which makes a grid or flex parent overflow rather than shrink. Let them be
   as narrow as their column: clipping one field beats a page that scrolls
   sideways. */
input[type=date], input[type=time], input[type=month], input[type=datetime-local] { min-width: 0; }
/* Primary action. A pill, because in this system a pill is the promise that
   something is tappable, and it is the only shape that carries the lime.
   The label is mono caps: short, urgent, and unmistakably a control rather
   than a sentence. The lime glow beneath is the app's one light source. */
button, .btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  margin-top: var(--sp-3);
  padding: .75rem 1.5rem;
  min-height: 44px;
  border: 0;
  border-radius: var(--r-pill);
  background: var(--action);
  color: var(--on-action);
  font-family: var(--font-control);
  font-weight: 700;
  font-size: .8125rem;
  letter-spacing: var(--control-tracking);
  text-transform: uppercase;
  cursor: pointer;
  text-decoration: none;
  box-shadow: var(--glow);
  background-image: var(--sheen);
  transition: transform .12s ease, box-shadow .12s ease, background-color .12s ease,
              filter .12s ease;
}
button:hover, .btn:hover { transform: translateY(-1px); box-shadow: 0 6px 26px color-mix(in srgb, var(--brand) 35%, transparent); }
button:active, .btn:active {
  transform: translateY(0) scale(.98);
  box-shadow: none;
  /* Dimming on press reads as physical in a way movement alone does not: the
     face darkens under the thumb rather than merely shrinking. */
  filter: brightness(.92);
}
button:focus-visible, .btn:focus-visible, a:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }
.btn svg, button svg { width: 20px; height: 20px; }
/* `background-color`, never the `background` shorthand: the shorthand resets
   `background-image` on its way past, which would strip the sheen off the one
   button it matters most on. Same for danger below. A variant that means to
   have no face -- ghost, secondary -- keeps the shorthand, and clearing the
   sheen is exactly what it should do there. */
.btn-brand { background-color: var(--brand-fill); color: var(--on-brand); }
/* Ghost: the secondary action. A 1px outline and no fill — it holds the same
   pill shape so it still reads as tappable, but casts no light, so it can
   never be mistaken for the primary. */
.btn-ghost, .ghost {
  background: transparent; color: var(--text);
  /* No sheen: the gradient describes light on a filled face, and an outlined
     control has none. Left on, it hangs in the middle of nothing. */
  background-image: none;
  border: 1px solid var(--outline); box-shadow: none;
}
.btn-ghost:hover, .ghost:hover { border-color: var(--brand); color: var(--brand-ink); box-shadow: none; transform: none; }
/* "‹ back to X" link above a page title. A navigational label, so it wears
   the label role rather than the prose one. */
.backlink {
  display: inline-flex; align-items: center; gap: var(--sp-1); margin-top: var(--sp-2);
  font-family: var(--font-mono); font-size: .7rem; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted); text-decoration: none;
}
.backlink:hover { color: var(--brand-ink); }
.backlink svg { width: 16px; height: 16px; flex: none; }
.providers a {
  display: block; margin: var(--sp-2) 0; padding: .75rem 1.5rem;
  border: 1px solid var(--outline); border-radius: var(--r-pill);
  text-align: center; text-decoration: none; color: var(--text);
  font-family: var(--font-control); font-size: .8125rem; font-weight: 700;
  letter-spacing: var(--control-tracking); text-transform: uppercase;
}
.providers a:hover { border-color: var(--brand); color: var(--brand-ink); }
.error { color: var(--error); margin: var(--sp-2) 0; }
.muted { color: var(--muted); }
.tiny { font-size: .85rem; line-height: 1.45; }
ul { padding-left: 1.1rem; }

/* ---- Shared redesign components ---- */
/* Big − / + stepper: value on top (a clear, tappable field), − / + split
   below. Grid areas place items independently of DOM order, so the flat
   markup (button, input, unit, button) — incl. JS-built rows — needs no change. */
.stepper {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-1);
  grid-template-areas: "field field" "minus plus";
  background: var(--panel-2); border-radius: var(--r-md); padding: var(--sp-1);
}
/* The value you are logging is a measurement, so it is set like every other
   measurement in the app: display face, heavy, tabular. */
.stepper input {
  grid-area: field; width: 100%; min-width: 0; text-align: center;
  font-family: var(--font-display); font-size: 1.5rem; font-weight: 800;
  font-variant-numeric: tabular-nums; cursor: text;
  border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--bg); color: var(--text);
  padding: .4rem 1.9rem; /* right room for the unit suffix */
}
.stepper .unit {
  grid-area: field; justify-self: end; align-self: center; pointer-events: none;
  font-family: var(--font-mono); color: var(--muted); font-weight: 600;
  font-size: .7rem; letter-spacing: .08em; text-transform: uppercase; padding-right: .6rem;
}
/* Compact: the row repeats per set, so every pixel here is paid for four
   times over. Still a 28px target, comfortably tappable. Charcoal, never
   lime — the accent belongs to the set's completion tick, not its nudges. */
.stepper button {
  margin: 0; padding: 0; height: 28px; min-height: 28px; border-radius: var(--r-sm);
  background: var(--panel-3); color: var(--text); font-family: var(--font-mono);
  /* Was .95rem at half-strength border: against a 1.5rem display value the
     glyphs read as grey slabs with something faint printed on them, which made
     a deliberately compact control look unfinished. The height is unchanged —
     it is paid for once per set and the 28px target was chosen on purpose —
     so the fix is contrast and glyph weight, not size. */
  font-size: 1.15rem; font-weight: 800; line-height: 1; letter-spacing: normal;
  border: 1px solid var(--border); box-shadow: none;
}
/* A press should feel like the value moved. Hover alone left the control inert
   on the one device that never hovers. */
.stepper button:hover { background: var(--bright); transform: none; box-shadow: none; }
.stepper button:active {
  background: var(--brand); color: var(--on-brand);
  border-color: var(--brand); transform: none;
}
.stepper button[data-step^="-"] { grid-area: minus; }
.stepper button:not([data-step^="-"]) { grid-area: plus; }
/* Type-ahead box above any exercise picker. Sits tight to the list it
   filters so the two read as one control. */
.exfilter { margin-bottom: .4rem; }

.field-label {
  display: flex; align-items: center; gap: var(--sp-2); margin: 0 0 var(--sp-1);
  font-family: var(--font-mono); font-size: .75rem; line-height: 1rem; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase; color: var(--muted);
}

/* A status chip. Reads as a label (mono caps), not as a button — it is
   outlined in its own colour rather than filled with it, which is what keeps
   a filled lime pill meaning "tap me". */
.pill {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  padding: .25rem .7rem; border-radius: var(--r-pill);
  border: 1px solid transparent;
  font-family: var(--font-mono); font-size: .75rem; line-height: 1rem; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase;
}
.pill svg { width: 15px; height: 15px; }
.pill.go { background: var(--go-soft); color: var(--go-ink); border-color: color-mix(in srgb, var(--go) 30%, transparent); }
.pill.brand { background: var(--brand-soft); color: var(--brand-ink); border-color: color-mix(in srgb, var(--brand) 30%, transparent); }
.pill.wait { background: var(--wait-soft); color: var(--wait-ink); border-color: color-mix(in srgb, var(--wait) 30%, transparent); }
.pill.info { background: color-mix(in srgb, var(--info) 14%, transparent); color: var(--info); border-color: color-mix(in srgb, var(--info) 30%, transparent); }

/* Segmented strip of tabs — weekday tabs on the log and the builder, the
   period switch on progress. One definition: three near-identical copies had
   drifted into three different pills for the same job. */
.switch { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin: var(--sp-1) 0 var(--sp-5); }
.switch .tab {
  padding: .5rem .9rem; border: 1px solid var(--border); border-radius: var(--r-pill);
  text-decoration: none; color: var(--muted); background: var(--panel);
  font-family: var(--font-control); font-size: .75rem; font-weight: 600;
  letter-spacing: var(--control-tracking); text-transform: uppercase;
  white-space: nowrap; cursor: pointer; transition: color .12s ease, border-color .12s ease;
}
.switch .tab:hover { color: var(--text); border-color: var(--outline); }
/* The selected range/day is the one lime thing in the strip. */
.switch .tab.active { background: var(--brand-fill); color: var(--on-brand); border-color: var(--brand); box-shadow: var(--glow-soft); }
/* A week is always seven days, so give them seven equal columns: the strip
   fits one line on a phone and every day sits where you last saw it. */
.switch.days { display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--sp-1); }
/* Seven narrow columns can't carry a pill or 0.1em tracking without
   clipping, so the day tabs keep the mono caps and drop both. */
.switch.days .tab { padding: .5rem .15rem; text-align: center; font-size: .7rem; letter-spacing: .02em; border-radius: var(--r-md); overflow: hidden; }
/* How many exercises that day holds — under the name, so a narrow column
   never has to choose between the two. */
.switch.days .tab .n { display: block; font-size: .62rem; font-weight: 800; line-height: 1; opacity: .75; margin-top: .1rem; }

/* Named days scroll; weekdays don't.
   The grid above works because a week is always seven three-letter labels. A
   flexible plan's days are the member's own words — "Day of Legs" — and there
   may be three of them or seven. Forcing those into seven equal columns is
   what clipped one to "DAY OF LE": the column cannot grow, and `overflow:
   hidden` finishes the job. So they get a scrolling strip of self-sized chips
   instead, which is the one layout that can hold a name of any length without
   either clipping it or squeezing its neighbours. */
.switch.days.named {
  display: flex; flex-wrap: nowrap; overflow-x: auto; gap: var(--sp-2);
  scroll-snap-type: x proximity; scrollbar-width: none;
  /* Bleed to the screen edges so the last chip is visibly cut off rather than
     ending flush, which is what tells a thumb there is more to scroll. */
  margin-inline: calc(var(--sp-4) * -1); padding-inline: var(--sp-4);
}
.switch.days.named::-webkit-scrollbar { display: none; }
.switch.days.named .tab {
  flex: 0 0 auto; scroll-snap-align: start; overflow: visible;
  padding: .55rem .9rem; font-size: .72rem; letter-spacing: var(--control-tracking);
  border-radius: var(--r-pill); text-align: left;
  /* The member typed this. Upper-casing "Push/Pull" is fine; upper-casing a
     name they capitalised themselves is us overruling them. */
  text-transform: none;
  min-height: var(--control-h);
  display: inline-flex; align-items: center; gap: .45rem;
}
/* Inline beside the name rather than stacked under it: there is room now. */
.switch.days.named .tab .n {
  display: inline-block; margin-top: 0; font-size: .68rem; opacity: 1;
  padding: .1rem .4rem; border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--text) 12%, transparent);
}
.switch.days.named .tab.active .n { background: color-mix(in srgb, var(--on-brand) 18%, transparent); }

/* ---- The day manager: one row per named day, and it stays one row.
   Every control sits on the line: the name, the save, the count, the remove.
   Stacking them turned a three-day plan into a column of anonymous boxes with
   no way to tell a rename field from the add-a-day field. ---- */
.daylist { display: flex; flex-direction: column; }
.dayrow {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) 0;
  border-top: 1px solid color-mix(in srgb, var(--border) 45%, transparent);
}
.dayrow:first-child { border-top: 0; }
/* The name takes the room; everything else is sized to its content. */
.dayrow form.rename { display: flex; align-items: center; gap: var(--sp-2); flex: 1 1 auto; min-width: 0; }
.dayrow form.rename input { flex: 1 1 auto; min-width: 0; margin: 0; }
.dayrow .count { flex: 0 0 auto; font-family: var(--font-control); font-size: .7rem; color: var(--muted); white-space: nowrap; }
.dayrow form { margin: 0; }
/* Quiet, not shouty: removing a day is destructive, but it is sitting next to
   a text field somebody is typing in. */
.dayrow .btn-quiet {
  background: none; border: 1px solid var(--outline); color: var(--muted);
  box-shadow: none; padding: .4rem .6rem; min-height: 0; font-size: .7rem;
}
.dayrow .btn-quiet:hover { color: var(--danger-ink); border-color: var(--danger-ink); transform: none; }
/* Adding is the primary action here, and is separated from the list so it is
   never mistaken for another rename field.

   The two gaps are deliberately unequal in the source and equal on screen: the
   last `.dayrow` already contributes its own `--sp-2` above the rule, so
   matching `padding-top` to `margin-top` would leave the line sitting closer to
   this row than to the list -- reading as part of the add row rather than as the
   thing dividing them. */
.dayadd {
  display: flex; align-items: center; gap: var(--sp-2); margin-top: var(--sp-2);
  padding-top: var(--sp-4); border-top: 1px solid var(--border);
}
/* The one question on the create form that changes how the plan behaves, so
   it reads as a decision rather than another field: the options are stacked and
   tappable whole, not a select nobody opens. */
.shape { border: 0; padding: 0; margin: var(--sp-3) 0 0; }
.shape legend { padding: 0; font-family: var(--font-control); font-size: .72rem;
  font-weight: 700; letter-spacing: var(--control-tracking); text-transform: uppercase;
  color: var(--muted); }
.shape .chk {
  display: flex; align-items: flex-start; gap: var(--sp-2);
  padding: var(--sp-3); margin-top: var(--sp-2);
  border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--panel-2); cursor: pointer;
}
.shape .chk:has(input:checked) { border-color: var(--brand); box-shadow: var(--glow-soft); }
.shape .chk input { margin: .15rem 0 0; accent-color: var(--brand); flex: none; }
/* `.chk` is mono caps, which is right for a one-word tick and wrong for a
   sentence: "I PICK THE DAY WHEN I GET THERE" is shouting. */
.shape .chk span {
  min-width: 0; text-transform: none; letter-spacing: normal;
  font-family: var(--font-body); font-size: .92rem;
}

.dayadd input { flex: 1 1 auto; min-width: 0; margin: 0; }
.dayadd button { flex: 0 0 auto; }

/* Big friendly list row (exercise / plan / person). */
/* The app's workhorse row — an exercise, a plan, a person. Charcoal on
   charcoal, lifting one tier on hover; the metadata under the name is mono
   caps, which is what makes a list of them scan like an instrument readout
   rather than prose. */
.lrow {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3); border: 1px solid color-mix(in srgb, var(--border) 45%, transparent);
  border-radius: var(--r-md); background: var(--panel); margin-bottom: var(--sp-2);
  text-decoration: none; color: var(--text); transition: background-color .12s ease, border-color .12s ease;
}
.lrow:hover { background: var(--panel-3); }
.lrow .thumb { width: 44px; height: 44px; border-radius: 50%; flex: none; display: grid; place-items: center; font-size: 1.3rem; background: var(--panel-3); }
.lrow .body { flex: 1; min-width: 0; }
.lrow .body b { display: block; font-size: 1rem; font-weight: 700; line-height: 1.25; }
.lrow .body span {
  display: block; margin-top: .15rem;
  font-family: var(--font-mono); font-size: .7rem; line-height: 1rem; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase; color: var(--muted);
}
/* A finished row is tinted, not repainted: the lime stays a hairline and a
   left edge so a long list of done rows doesn't glare. */
.lrow.done { background: var(--go-soft); border-color: color-mix(in srgb, var(--go) 25%, transparent); }
.lrow .go-ic { width: 32px; height: 32px; border-radius: 50%; background: var(--go-soft); color: var(--go-ink); display: grid; place-items: center; flex: none; }
.lrow .go-ic svg { width: 18px; height: 18px; }
/* Systematic index (01, 02, 03) down the left of a log or set list. */
.lrow .idx, .idx {
  font-family: var(--font-mono); font-size: .75rem; font-weight: 700;
  letter-spacing: .08em; color: var(--muted); font-variant-numeric: tabular-nums;
}

/* ---- Bottom tab bar (primary navigation) ---- */
/* The bar floats over the content it scrolls above: the lowest surface tier,
   blurred, with a hairline of lime cast upward so it reads as lit furniture
   rather than a wall. */
.tabbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 45;
  display: flex; gap: .25rem;
  padding: var(--sp-2) var(--sp-2) calc(var(--sp-2) + env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--panel-low) 90%, transparent);
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
  border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
  border-radius: var(--r) var(--r) 0 0;
  box-shadow: 0 -4px 20px color-mix(in srgb, var(--brand) 8%, transparent);
}
.tabbar-inner { display: flex; gap: .25rem; width: 100%; max-width: 40rem; margin: 0 auto; }
/* On a wide window the bar was the one full-bleed element on the page: a strip
   welded across 90rem while everything above it sat in a 48rem column, so it
   read as browser chrome rather than part of the app. Floated and rounded on
   all four corners, it becomes a control resting on the page — the same shape
   the phone app's own bar has, so the two surfaces stop disagreeing. */
@media (min-width: 64rem) {
  .tabbar {
    left: 50%; right: auto; transform: translateX(-50%);
    width: min(40rem, calc(100% - 2 * var(--sp-6)));
    bottom: calc(var(--sp-4) + env(safe-area-inset-bottom));
    /* Even padding all round. The docked bar padded its bottom edge for the
       home indicator; keeping that here left the selected pill sitting proud
       of the bar's own rounding. */
    padding: var(--sp-2);
    border: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow), 0 8px 28px rgba(0, 0, 0, .45);
  }
}
.tab-item {
  /* min-width:0 so five items always divide the bar instead of pushing the
     last one ("More") off the edge of a 360px phone. */
  flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: center; gap: .15rem;
  padding: .35rem .25rem; border-radius: var(--r-md);
  text-decoration: none; color: var(--muted);
  font-family: var(--font-mono); font-size: .65rem; font-weight: 600; letter-spacing: .04em;
  transition: color .12s ease;
}
/* "More" trigger sits in the tab bar as a button — strip the default button chrome. */
.tabbar button.tab-item { background: none; margin: 0; min-height: 0; box-shadow: none; cursor: pointer; }
/* The pill around the icon is what sets each item's width; let it give ground
   on a narrow screen rather than overflow the bar. */
.tab-item .tab-ic { display: flex; padding: .1rem clamp(.3rem, 2.5vw, .8rem); border-radius: var(--r-pill); }
.tab-item svg { width: 24px; height: 24px; }
.tab-item:hover { color: var(--text); }
/* Where you are is a filled, glowing lime pill wrapping icon *and* label —
   the same shape and colour as the primary action, because "here" and "go"
   are the same energy. */
.tab-item.active {
  background: var(--brand-fill); color: var(--on-brand);
  border-radius: var(--r-pill); box-shadow: var(--glow-soft);
}

/* ---- Shared confirmation modal (replaces native confirm()). ---- */
.modal-overlay { position: fixed; inset: 0; z-index: 100; display: flex; align-items: center; justify-content: center; padding: 1rem; background: rgba(0,0,0,.5); }
.modal-overlay[hidden] { display: none; }
.modal-card { width: 100%; max-width: 23rem; background: var(--panel-3); border: 1px solid var(--border); border-radius: var(--r-lg); padding: var(--sp-5); box-shadow: var(--shadow), 0 16px 48px rgba(0,0,0,.6); }
.modal-card h3 { margin: 0 0 var(--sp-2); font-size: 1.5rem; }
.modal-msg { color: var(--muted); margin: 0; font-size: 1rem; }
.modal-actions { display: flex; gap: var(--sp-3); margin-top: var(--sp-5); }
.modal-actions button { flex: 1; margin: 0; }
.btn-secondary { background: transparent; color: var(--text); border: 1px solid var(--outline); box-shadow: none; }
.btn-secondary:hover { border-color: var(--brand); color: var(--brand-ink); box-shadow: none; }
/* Confirming a destruction is not a "go" — it must never wear the lime. */
.btn-danger { background-color: var(--error); color: #1b1b1f; box-shadow: none; }
.btn-danger:hover { box-shadow: none; filter: brightness(1.05); }

/* One unmistakable primary action per screen: full width, nothing beside it. */
.primary-cta { width: 100%; font-size: .875rem; padding: 1rem 1.5rem; }
/* Secondary actions under it — equal weight, never mistaken for the primary. */
.toolbar { display: flex; gap: .4rem; flex-wrap: wrap; margin: .5rem 0 .2rem; }
.toolbar form { display: inline; margin: 0; }
/* Destructive actions read as destructive and sit apart from everything else. */
.danger { background: transparent; color: var(--error); border: 1px solid var(--error); box-shadow: none; }
.danger:hover { background: color-mix(in srgb, var(--error) 12%, transparent); box-shadow: none; border-color: var(--error); color: var(--error); }
.danger-zone { margin: var(--sp-4) 0 0; padding-top: var(--sp-3); border-top: 1px solid var(--border); }
/* Keeps a date, a measurement or any short pair from breaking mid-value. */
.nw { white-space: nowrap; }

@media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }

/* ============ 3. PER-PAGE BLOCKS (.p-<name>) ============ */
/* (moved from each template's <style> during the sweep) */

/* ============ 4. UTILITIES ============
   Named for what they mean, and global — a `.mt-3` is the same nudge on every
   page. What is left below as `.p-<page> .sN` is a visual that genuinely
   belongs to one screen; anything shared has a name here instead. Reach for
   these before writing a new page rule, and never for an inline style. */

/* Spacing, on the token scale. */
.m-0 { margin: 0; }
.mb-0 { margin-bottom: 0; }
.mb-1 { margin-bottom: var(--sp-1); }
.mb-3 { margin-bottom: var(--sp-3); }
.mb-4 { margin-bottom: var(--sp-4); }
.mt-0 { margin-top: 0; }
.mt-1 { margin-top: var(--sp-1); }
.mt-2 { margin-top: var(--sp-2); }
.mt-3 { margin-top: var(--sp-3); }
.mt-4 { margin-top: var(--sp-4); }
/* Pulls a block up under a heading that already carries its own gap. */
.mt-pull { margin-top: -.45rem; }
.my-1 { margin: var(--sp-1) 0; }
.my-2 { margin: var(--sp-2) 0; }

/* Layout. */
.full { width: 100%; }
.center { text-align: center; }
/* A title with something opposite it — the app's most repeated row. */
.row-between { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3); flex-wrap: wrap; }
.row-between.row-center { align-items: center; }
.grid-2-1 { display: grid; grid-template-columns: 2fr 1fr; gap: var(--sp-3); }

/* Type + colour. */
.small { font-size: .8rem; }
.go-ink { color: var(--go-ink); }
.tap { cursor: pointer; font-weight: 600; }
/* "DELOAD" and friends: a warning that is a label, not a button. */
.tag-warn { font-size: .72rem; font-weight: 700; color: var(--wait-ink); border: 1px solid var(--wait);
  border-radius: 6px; padding: .05rem .35rem; vertical-align: middle; }


/* calendar */
.p-calendar .cal-head { display:flex; align-items:center; justify-content:space-between; gap:.6rem; margin-bottom:.9rem; }
.p-calendar .cal-head h2 { margin:0; font-size:1.2rem; }
.p-calendar .cal-nav { width:46px; height:46px; border:1.5px solid var(--border); border-radius:12px; display:grid; place-items:center; text-decoration:none; color:var(--text); background:var(--panel); }
.p-calendar .cal-nav svg { width:22px; height:22px; }
.p-calendar .cal-grid { display:grid; grid-template-columns:repeat(7,1fr); gap:5px; }
.p-calendar .dow { text-align:center; font-size:.72rem; font-weight:800; color:var(--muted); padding:2px 0; }
.p-calendar .cell { aspect-ratio:1; border-radius:11px; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:2px; font-size:.95rem; font-weight:750; background:var(--panel-2); position:relative; }
.p-calendar .cell.blank { background:transparent; }
.p-calendar .cell .d { line-height:1; }
.p-calendar .cell .mk { font-size:.9rem; line-height:1; }
/* worked out = green; planned = dashed outline; missed = amber */
.p-calendar .cell.done, .p-calendar .cell.logged { background:var(--go); color:var(--on-go); }
.p-calendar .cell.missed { background:var(--wait-soft); color:var(--wait-ink); }
.p-calendar .cell.planned { background:var(--panel); border:2px dashed var(--border); }
.p-calendar .cell.today { outline:3px solid var(--brand); outline-offset:1px; }
a.cell { text-decoration:none; color:inherit; cursor:pointer; transition:transform .06s ease, filter .12s ease; }
a.cell:hover { filter:brightness(1.08); transform:scale(1.04); }
a.cell:active { transform:scale(.97); }
.p-calendar .legend { display:flex; flex-direction:column; gap:.7rem; margin-top:.2rem; }
.p-calendar .legend .li { display:flex; align-items:center; gap:.7rem; font-weight:700; }
.p-calendar .swatch { width:30px; height:30px; border-radius:9px; flex:none; display:grid; place-items:center; font-size:.85rem; }
.p-calendar .s3 { margin-bottom:6px; }
.p-calendar .s4 { background:var(--go);color:var(--on-go); }
.p-calendar .s5 { background:var(--panel);border:2px dashed var(--border); }
.p-calendar .s6 { background:var(--wait-soft); }
.p-calendar .s7 { outline:3px solid var(--brand);outline-offset:-3px; }

/* dashboard */
.p-dashboard .net-figure { font-size:1.3rem; }
.p-dashboard .greet { margin: .2rem 0 1rem; }
.p-dashboard .greet .eyebrow { margin: 0 0 var(--sp-1); }
.p-dashboard .greet h1 { margin: 0; }

/* Celebration / streak banners */
.p-dashboard .banner { display: flex; align-items: center; gap: .9rem; border-radius: var(--r); padding: 1rem 1.15rem; margin-bottom: 1rem; }
.p-dashboard .banner .emo { font-size: 1.9rem; line-height: 1; flex: none; }
.p-dashboard .banner b { display: block; font-size: 1.1rem; }
.p-dashboard .banner.win { background: var(--go-soft); }
.p-dashboard .banner.streak { background: var(--wait-soft); }

/* Today hero */
.p-dashboard .hero { position: relative; overflow: hidden; }
.p-dashboard .hero-row { display: flex; align-items: center; gap: 1.1rem; }
.p-dashboard .ring { --p: 0; width: 92px; height: 92px; flex: none; border-radius: 50%;
    /* Progress wears the member's colour, like every other thing they can act
       on. What a set or a day *means* — done, missed, careful — keeps its
       semantic colour; how far through today you are is not a meaning. */
    background: conic-gradient(var(--action) calc(var(--p) * 1%), var(--panel-2) 0); display: grid; place-items: center; position: relative; }
.p-dashboard .ring::after { content: ""; position: absolute; inset: 10px; border-radius: 50%; background: var(--panel); }
/* z-index: the ::after disc is the ring's last child and would otherwise
   paint over the count and hide it. */
.p-dashboard .ring .mid { position: relative; z-index: 1; text-align: center; line-height: 1; }
.p-dashboard .ring .mid b { font-size: 1.5rem; font-weight: 850; }
.p-dashboard .ring .mid span { display: block; font-size: .7rem; color: var(--muted); font-weight: 700; margin-top: .1rem; }
.p-dashboard .hero .plan-name { font-size: 1.4rem; font-weight: 850; letter-spacing: -.02em; margin: .35rem 0 .15rem; }

/* Simple "today's sets" list */
.p-dashboard .setline { display: flex; align-items: center; gap: .7rem; padding: .6rem 0; border-top: 1px solid var(--border); }
.p-dashboard .setline:first-child { border-top: 0; }
.p-dashboard .setline .dot { width: 26px; height: 26px; border-radius: 50%; background: var(--go-soft); color: var(--go-ink); display: grid; place-items: center; flex: none; }
.p-dashboard .setline .dot svg { width: 15px; height: 15px; }
.p-dashboard .setline .nm { flex: 1; min-width: 0; font-weight: 700; }
.p-dashboard .setline .amt { font-weight: 800; }
.p-dashboard .setline .amt small { color: var(--muted); font-weight: 600; }

.p-dashboard .two { display: grid; grid-template-columns: 1fr 1fr; gap: .7rem; }
.p-dashboard .disclose > summary { cursor: pointer; list-style: none; font-weight: 800; font-size: 1.1rem; display: flex; align-items: center; justify-content: space-between; }
.p-dashboard .disclose > summary::-webkit-details-marker { display: none; }
.p-dashboard .disclose > summary .chev { transition: transform .2s; color: var(--muted); }
.p-dashboard .disclose[open] > summary .chev { transform: rotate(90deg); }
.p-dashboard .disclose { margin-bottom: 1rem; }

/* Converted inline styles */
.p-dashboard .s1 { --p: 100; }
.p-dashboard .ring .mid svg { stroke: var(--action); }
.p-dashboard .s3 { font-size: 1.6rem; }
.p-dashboard .s5 { margin: .2rem 0 0; }
.p-dashboard .s6 { border-color: var(--brand); }
.p-dashboard .s9 { font-weight: 700; }
.p-dashboard .s13 { margin-bottom: 1rem; }
.p-dashboard .s14 { margin-top: .5rem; }
.p-dashboard .s15 { display: flex; gap: .5rem; align-items: stretch; }
.p-dashboard .s16 { margin-top: 0; flex: none; }
.p-dashboard .s18 { display: flex; align-items: baseline; gap: 1.2rem; flex-wrap: wrap; margin-bottom: .6rem; }
.p-dashboard .s19 { font-size: 1.3rem; }
.p-dashboard .s20 { display: flex; gap: .6rem; flex-wrap: wrap; align-items: flex-end; }
.p-dashboard .s21 { flex: 1; min-width: 6rem; }
.p-dashboard .s22 { flex: 1; min-width: 5rem; }
.p-dashboard .s23 { margin-top: 1.4rem; }
.p-dashboard .s24 { flex: 1; min-width: 7rem; }
.p-dashboard .s25 { margin-top: .7rem; font-weight: 700; }

/* exercise_detail */
.p-exercise_detail .mgrid { display:grid; grid-template-columns:repeat(auto-fill,minmax(14rem,1fr)); gap:1rem; }
.p-exercise_detail .mcard img, .p-exercise_detail .mcard video, .p-exercise_detail .mcard iframe { width:100%; border-radius:10px; display:block; aspect-ratio:16/10; object-fit:cover; background:var(--panel); border:0; }
.p-exercise_detail .mcard .cap { font-size:.8rem; color:var(--muted); margin-top:.3rem; }
.p-exercise_detail .doc { display:flex; align-items:center; justify-content:center; aspect-ratio:16/10; background:var(--panel); border:1px solid var(--border); border-radius:10px; font-size:2.5rem; text-decoration:none; }
.p-exercise_detail .tags { display:flex; gap:.4rem; flex-wrap:wrap; margin:.5rem 0; }
.p-exercise_detail .tag { font-size:.8rem; border:1px solid var(--border); border-radius:999px; padding:.15rem .6rem; color:var(--muted); }

/* invite */
.p-invite .s1 { max-width:30rem; margin:2rem auto; }

/* leaderboard */
.p-leaderboard .rank { display:flex; align-items:center; gap:.9rem; padding:.9rem 1rem; border:1px solid var(--border); border-radius:16px; background:var(--panel); box-shadow:var(--shadow); margin-bottom:.7rem; }
.p-leaderboard .rank .n { width:44px; text-align:center; font-size:1.5rem; font-weight:850; flex:none; color:var(--muted); }
.p-leaderboard .rank .n.top { font-size:1.7rem; }
.p-leaderboard .rank .body { flex:1; min-width:0; }
.p-leaderboard .rank .body b { display:block; font-size:1.1rem; font-weight:800; }
.p-leaderboard .rank .body span { font-size:.85rem; color:var(--muted); font-weight:600; }

/* log_today */
.p-stats .mtrend { display:flex; align-items:center; gap:.6rem; margin:-.2rem 0 .6rem; }
.p-stats .mtrend .spark { display:flex; align-items:flex-end; gap:1px; height:1.1rem; flex:1; min-width:0; }
.p-stats .mtrend .spark i { flex:1; background:var(--muted); opacity:.5; border-radius:1px 1px 0 0; min-height:1px; }
.p-stats .right { text-align:right; }

/* One meter for the app. Everything that reports "how far along is this" —
   a strength standard, muscle coverage, today's sets, recovery — draws the same
   bar, so a member learns to read it once. Colour comes from a `band-*` class
   on the same element or its parent, which is `set_band`'s vocabulary. */
.meter { height:.5rem; border-radius:999px; background:var(--panel-3); overflow:hidden; margin:.45rem 0; }
.meter > span { display:block; height:100%; background:var(--brand); border-radius:999px; }
.meter.low > span, .band-low > .meter > span { background:var(--wait); }
.meter.high > span, .band-high > .meter > span { background:var(--error); }
.mtag.band-untrained, .mtag.band-low { color:var(--wait); }
.mtag.band-novice, .mtag.band-fair { color:var(--text); }
.mtag.band-intermediate, .mtag.band-good, .mtag.band-ok { color:var(--go-ink); }
.mtag.band-advanced { color:var(--brand-ink); }
.p-records .stand { padding:.7rem 0; border-top:1px solid var(--border); }
.p-records .stand:first-of-type { border-top:0; }

/* The weight helper: a button under the weight box, and one sheet for the
   whole page. There is only ever one calculator open, and a dozen set rows on
   a busy day, so a panel per row would be markup nobody sees. */
/* The suggestion. Quiet by default and only faintly accented when it says to
   add — it is advice, not a result, and should not compete with the set the
   member is about to log. */
.p-log_today .advice { border:1px dashed var(--border); border-radius:var(--r-md); padding:.6rem .8rem; margin-bottom:.9rem; }
.p-log_today .advice-add { border-color:var(--outline); }
.p-log_today .usebtn { background:none; border:1px solid var(--border); color:var(--text); border-radius:var(--r-sm); padding:.3rem .7rem; font-size:.78rem; font-weight:700; cursor:pointer; }
.p-log_today .usebtn:disabled { opacity:.45; cursor:default; }
.p-log_today .calcbtn { margin-top:.4rem; width:100%; background:none; border:1px dashed var(--border); color:var(--muted); font-size:.78rem; font-weight:700; padding:.35rem; border-radius:8px; cursor:pointer; }
.p-log_today .calcbtn:hover { color:var(--text); border-color:var(--outline); }
/* How hard the set was. Deliberately the quietest control on the row: the
   numbers a member came to log are the weight and the reps, and most never
   record an effort score at all. Label beside the box rather than above it, so
   an optional field costs one line rather than two per set. */
.rpe { display:flex; align-items:center; gap:.4rem; margin-top:.4rem; }
.rpe select { flex:1; min-width:0; text-align:center; background:var(--bg); color:var(--text);
  border:1px solid var(--border); border-radius:var(--r-sm); padding:.25rem .3rem;
  font-family:var(--font-mono); font-variant-numeric:tabular-nums; font-size:.85rem; }
.p-log_today .calc { position:fixed; inset:0; z-index:60; background:rgba(0,0,0,.55); display:flex; align-items:flex-end; justify-content:center; }
.p-log_today .calc-card { background:var(--panel); border-top:1px solid var(--border); border-radius:16px 16px 0 0; padding:1.1rem; width:100%; max-width:32rem; max-height:85vh; overflow-y:auto; }
.p-log_today .calc-x { background:none; border:0; color:var(--muted); font-size:1.1rem; cursor:pointer; padding:.2rem .4rem; }
/* Chips you tap, the way you load a bar: plates come off the rack one at a
   time. A counter per denomination was seven rows of form to fill in between
   sets — and with the minus buttons wrapping out of sight, it did not even
   offer a way back. Tapping a loaded plate takes it off. */
.p-log_today .plates { display:flex; flex-wrap:wrap; gap:.4rem; }
.p-log_today .loaded { display:flex; flex-wrap:wrap; gap:.4rem; margin-top:.7rem; min-height:2rem; }
.p-log_today .chip { padding:.5rem .8rem; border-radius:999px; border:1px solid var(--border); background:var(--panel-3); color:var(--text); font-weight:800; font-variant-numeric:tabular-nums; font-size:.9rem; cursor:pointer; }
.p-log_today .chip:hover { border-color:var(--outline); }
.p-log_today .chip.on { background:var(--brand); border-color:var(--brand); color:var(--on-brand); }
.p-log_today .chip.clear { background:none; border-style:dashed; color:var(--muted); font-weight:700; }
.p-log_today .barline { display:flex; align-items:center; gap:.6rem; margin-bottom:.9rem; }
.p-log_today .barline > span:first-child { color:var(--muted); font-size:.8rem; font-weight:700; }
.p-log_today .barline input { flex:0 0 5.5rem; text-align:center; font-weight:800; font-variant-numeric:tabular-nums; padding:.45rem; border:1px solid var(--border); border-radius:var(--r-sm); background:var(--bg); color:var(--text); }
.p-log_today .barline .unit { color:var(--muted); font-family:var(--font-mono); font-size:.72rem; text-transform:uppercase; letter-spacing:.08em; }
.p-log_today .calc-sum { margin:.9rem 0; font-weight:700; font-variant-numeric:tabular-nums; text-align:center; }
.p-log_today .top { display:flex; align-items:flex-start; justify-content:space-between; gap:1rem; }
.p-log_today .top h1 { margin-bottom:.2rem; }


.p-log_today .ex-head { display:flex; align-items:flex-start; justify-content:space-between; gap:.6rem; margin-bottom:.9rem; }
/* The title takes the slack so the targets stay pinned right even with the
   illustration tile now sharing this row. */
.p-log_today .ex-head h2 { margin:0; display:flex; align-items:center; gap:.5rem; flex:1 1 auto; min-width:0; }
.p-log_today .ex-target { font-size:.82rem; color:var(--muted); font-weight:700; text-align:right; flex:none; }
.p-log_today .check { color:var(--go-ink); }

.p-log_today .setrow { position:relative; border:1px solid color-mix(in srgb, var(--border) 50%, transparent); border-radius:var(--r-md); padding:.65rem; margin-bottom:.55rem; overflow:hidden; }
.p-log_today .setrow.done { background:var(--go-soft); border-color:color-mix(in srgb, var(--brand) 25%, transparent); }
.p-log_today .setrow.done::before { content:""; position:absolute; left:0; top:0; bottom:0; width:3px; background:var(--go); }
.p-log_today .setrow-head { display:flex; align-items:center; justify-content:space-between; margin-bottom:.55rem; }
.p-log_today .setrow-head b { font-family:var(--font-mono); font-size:.72rem; font-weight:700; letter-spacing:.1em; text-transform:uppercase; }
.p-log_today .two { display:grid; grid-template-columns:1fr 1fr; gap:.5rem; }
.p-log_today .warm-toggle { display:flex; align-items:center; gap:.5rem; margin-top:.6rem; font-size:.85rem; font-weight:650; color:var(--muted); cursor:pointer; }
.p-log_today .warm-toggle input { width:20px; height:20px; flex:none; accent-color:var(--wait); }
/* Removing a set is rare — the boxed button shouted louder than the row's
   status. Quiet by default, unmistakably destructive once you reach for it.
   Keeps its 40px touch target. */
.p-log_today .delset { margin:0; width:40px; height:34px; min-height:0; padding:0; background:transparent;
  border:0; box-shadow:none; color:var(--muted); font-weight:800; font-size:1rem; border-radius:9px; }
.p-log_today .delset:hover, .p-log_today .delset:focus-visible {
  background:color-mix(in srgb, var(--error) 12%, transparent); color:var(--error); }

.p-log_today .addset { background:transparent; border:1px dashed var(--outline); color:var(--muted); box-shadow:none; width:100%; }

.p-log_today .rest { text-align:center; background:var(--wait-soft); border-radius:12px; padding:.7rem; margin-top:.2rem; }
.p-log_today .rest p { margin:0 0 .6rem; font-weight:800; color:var(--wait-ink); }
.p-log_today .rest-btns { display:flex; gap:.5rem; justify-content:center; }
.p-log_today .rest-btn { margin:0; background:transparent; color:var(--text); border:1px solid var(--outline); box-shadow:none; min-height:42px; flex:1; }
.p-log_today .rest-clock { font-family:var(--font-mono); font-size:1.75rem; font-weight:700; font-variant-numeric:tabular-nums; margin-top:.6rem; }
.p-log_today .rest-clock.done { color:var(--go-ink); }
.p-log_today .hide { display:none !important; }

.p-log_today .s1 { background:var(--go-soft); border-color:transparent; }
.p-log_today .s2 { background:var(--wait-soft); border-color:transparent; }
.p-log_today .s3 { display:flex; align-items:center; gap:.4rem; }
.p-log_today .s4 { margin-top:.6rem; flex:none; }
.p-log_today .s7 { text-align:center; font-weight:700; }

/* login */
.p-login .s1 { max-width:26rem; margin:2rem auto; }
.p-login .s2 { text-align:center; margin:.25rem 0 1rem; }
.p-login .s3 { width:100%; max-width:240px; height:auto; }
.p-login .s4 { margin-top:1.5rem; }

/* manage_exercise */
.p-manage_exercise .mgrid { display:grid; grid-template-columns:repeat(auto-fill,minmax(11rem,1fr)); gap:1rem; }
.p-manage_exercise .mcard { border:1px solid var(--border); border-radius:10px; padding:.6rem; }
.p-manage_exercise .mcard img, .p-manage_exercise .mcard video, .p-manage_exercise .mcard iframe { width:100%; border-radius:var(--r-sm); display:block; aspect-ratio:16/10; object-fit:cover; background:var(--bg); border:0; }
/* The card crops to 16:10, so the picture has to be openable to be checked. */
.p-manage_exercise .mcard .mshow { display:block; width:100%; padding:0; margin:0; border:0;
  background:none; box-shadow:none; min-height:0; border-radius:var(--r-sm); cursor:zoom-in; }
.p-manage_exercise .mcard .mshow:hover img { outline:2px solid var(--brand); outline-offset:-2px; }
.p-manage_exercise .mcard .cap { font-size:.8rem; color:var(--muted); margin:.35rem 0; display:flex; justify-content:space-between; align-items:center; gap:.4rem; }
.p-manage_exercise .mcard .role { font-size:.7rem; text-transform:uppercase; letter-spacing:.03em; border:1px solid var(--border); border-radius:5px; padding:0 .3rem; }
.p-manage_exercise .doc { display:flex; align-items:center; justify-content:center; aspect-ratio:16/10; background:var(--bg); border-radius:var(--r-sm); font-size:2rem; }
.p-manage_exercise .muscols { display:grid; grid-template-columns:repeat(auto-fit,minmax(9rem,1fr)); gap:.8rem; }
.p-manage_exercise .muscols label { display:flex; align-items:center; gap:.4rem; margin:.15rem 0; font-size:.9rem; color:var(--text); }
.p-manage_exercise .muscols input { width:auto; margin:0; }
.p-manage_exercise .delmini { margin:0; padding:.1rem .4rem; background:none; border:1px solid var(--border); border-radius:6px; color:var(--brand-ink); cursor:pointer; }
.p-manage_exercise .s8 { font-size:.85rem; }
.p-manage_exercise .s9 { flex:1; }
.p-manage_exercise .s10 { display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin-top:1rem; }
.p-manage_exercise .s11 { margin:0 0 .3rem; }
.p-manage_exercise .s12 { font-size:.8rem;margin-top:0; }

/* Admin data tables — the catalog and the member list share one look. */
.admin-table { width:100%; border-collapse:collapse; }
.admin-table th, .admin-table td { text-align:left; padding:.55rem .5rem; border-bottom:1px solid var(--border); vertical-align:middle; }
.admin-table th { color:var(--muted); font-size:.8rem; text-transform:uppercase; letter-spacing:.03em; }
.admin-table tbody tr:last-child td { border-bottom:0; }
.admin-table tbody tr:hover { background:var(--panel-2); }
.admin-table .actions { display:flex; gap:.4rem; justify-content:flex-end; }
.admin-table .actions form { margin:0; }
.admin-table .actions a, .admin-table .actions button { margin:0; padding:.3rem .6rem; font-size:.85rem; }
.admin-table .cname a { color:var(--text); text-decoration:none; }
.admin-table .cname a:hover { color:var(--brand-ink); text-decoration:underline; }
/* Delete sits next to Edit all day — keep it quiet until you reach for it. */
.admin-table .actions button[type=submit].ghost { color:var(--muted); }
.admin-table .actions button[type=submit].ghost:hover { color:var(--error); border-color:var(--error); }
/* A table that outgrows its panel scrolls sideways rather than overflowing. */
.wrap { overflow-x:auto; }


/* manage_exercises */
/* Media column: thumb + chips stay on one line so rows keep a single-row
   height; the table scrolls sideways on narrow screens instead. */
.p-manage_exercises td.cmedia, .p-manage_exercises th.cmedia { width:1%; white-space:nowrap; }
.p-manage_exercises .ex-media { flex-wrap:nowrap; }
/* Phones: drop the secondary columns (both live on the exercise's own page)
   so the name, media and actions fit without a sideways scroll. */
@media (max-width: 48rem) { .p-manage_exercises .csec { display:none; } }
.p-manage_exercises .catlist > input[type=search] { margin-bottom:.6rem; }
/* Desktop: the page gets more room, the create form parks beside the catalog
   and follows it down the list. Phones keep the single stacked column. */
@media (min-width: 64rem) {
  .p-manage_exercises main { max-width: 76rem; }
  .p-manage_exercises .catshell { display:grid; grid-template-columns:22rem 1fr; gap:1rem; align-items:start; }
  .p-manage_exercises .catshell > details { position:sticky; top:.85rem; margin-bottom:0; }
}
.p-manage_exercises .s1 { font-size:.9rem;font-weight:400; }

/* mcp_key */
.p-mcp_key .secret { display:flex; gap:.5rem; align-items:center; }
.p-mcp_key .secret input { font-family:ui-monospace,monospace; font-size:.95rem; background:var(--panel); }
.p-mcp_key .copy { margin:0; white-space:nowrap; }
.p-mcp_key .warn { border-color:var(--brand); }
.p-mcp_key code { word-break:break-all; }
.p-mcp_key .s1 { border-color:var(--brand); }
.p-mcp_key .s2 { width:auto; }
.p-mcp_key .s3 { padding-right:1rem; }
.p-mcp_key .s4 { font-size:.9rem; }
.p-mcp_key .s5 { background:var(--brand-fill); color:var(--on-brand); border-color:var(--brand); }

/* muscle_map */
.p-muscle_map .muscle-map .bodymap svg { width: 280px; max-width: 46vw; }
.p-muscle_map .mm-legend { display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: .6rem; margin: 1rem 0; }
.p-muscle_map .mm-legend .row { display: flex; align-items: flex-start; gap: .6rem; border: 1px solid var(--border); border-radius: 10px; padding: .6rem .8rem; }
.p-muscle_map .mm-legend .sw { flex: 0 0 auto; width: 1.4rem; height: 1.4rem; border-radius: 6px; margin-top: .1rem; }
.p-muscle_map .mm-legend .name { font-weight: 700; }
.p-muscle_map .mm-legend .covers { color: var(--muted); font-size: .82rem; }
.p-muscle_map .mm-note { color: var(--muted); font-size: .85rem; }
.p-muscle_map .s-mm-h { margin-top: 1.4rem; }

/* profile */
/* Folded behind its own switch: eight body-part questions are an intrusive
   thing to put in front of the overwhelming majority who do not need them. */
.p-profile .limbset > summary { cursor:pointer; padding:.5rem 0; }
.p-profile .limbset[open] > summary { margin-bottom:.2rem; }
/* Eight limbs in two columns, each its own form so one answer submits alone.
   A grid rather than a table: on a phone the labels and selects need to stack,
   and a table would make eight rows of two cramped cells. */
.p-profile .limbs { display:grid; grid-template-columns:repeat(auto-fit,minmax(9rem,1fr)); gap:.6rem .8rem; margin-top:.6rem; }
.p-profile .limb { display:flex; flex-direction:column; gap:.2rem; margin:0; }
.p-profile .limb label { font-size:.85rem; color:var(--muted); text-transform:capitalize; }
.p-profile .limb select { width:100%; }
.p-profile .tiles { display:grid; grid-template-columns:repeat(2,1fr); gap:.7rem; }
.p-profile .tile { border:1px solid color-mix(in srgb, var(--border) 45%, transparent); border-radius:var(--r-md); padding:.85rem 1rem; background:var(--panel); }
.p-profile .tile b { display:block; font-family:var(--font-display); font-size:1.5rem; font-weight:800; letter-spacing:-.01em; line-height:1.1; font-variant-numeric:tabular-nums; }
.p-profile .tile span { display:block; margin-top:var(--sp-1); color:var(--muted); font-family:var(--font-mono); font-size:.65rem; line-height:.9rem; font-weight:600; letter-spacing:.08em; text-transform:uppercase; }

.p-profile .seg { display:flex; gap:.35rem; background:var(--panel-2); border-radius:var(--r-pill); padding:.3rem; margin:0; }
.p-profile .seg button { flex:1; margin:0; min-height:44px; background:transparent; color:var(--muted); box-shadow:none; border-radius:var(--r-pill); font-size:.75rem; font-family:var(--font-control); letter-spacing:var(--control-tracking); }
.p-profile .seg button.on { background:var(--brand-fill); color:var(--on-brand); box-shadow:var(--glow-soft); }

.p-profile .grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(9rem,1fr)); gap:.4rem 1rem; }
/* A date field needs more room than a number field: give it the whole row
   rather than widening every column and halving the form's density. */
.p-profile .grid > div:has(input[type=date]) { grid-column:1 / -1; }

.p-profile .disclose > summary { cursor:pointer; list-style:none; font-weight:800; font-size:1.15rem; display:flex; align-items:center; justify-content:space-between; }
.p-profile .disclose > summary::-webkit-details-marker { display:none; }
.p-profile .disclose > summary .chev { color:var(--muted); transition:transform .2s; }
.p-profile .disclose[open] > summary .chev { transform:rotate(90deg); }
.p-profile .disclose { margin-bottom:1rem; }

.p-profile .hrow { display:flex; align-items:center; gap:.8rem; padding:.7rem 0; border-top:1px solid var(--border); }
.p-profile .hrow:first-child { border-top:0; }
.p-profile .hrow .d { font-family:var(--font-mono); font-size:.72rem; font-weight:600; letter-spacing:.06em; }
.p-profile .hrow .v { margin-left:auto; text-align:right; }
.p-profile .rm { margin:0; min-height:0; padding:.35rem .7rem; background:transparent; color:var(--muted); border:1px solid var(--border); box-shadow:none; font-size:.65rem; }
.p-profile code { background:var(--panel-2); padding:.15rem .4rem; border-radius:6px; font-size:.9rem; }

.p-profile .s2 { width:76px; height:76px; border-radius:50%; background:var(--brand-soft); display:grid; place-items:center; font-size:38px; margin:0 auto .5rem; }
.p-profile .s3 { margin-top:.8rem; grid-template-columns:repeat(4,1fr); }
/* Four stat tiles side by side squeeze the labels on a narrow phone. */
@media (max-width: 26rem) { .p-profile .s3 { grid-template-columns:repeat(2,1fr); } }
.p-profile .tile { padding:.7rem .6rem; text-align:center; }
.p-profile .s6 { margin:1rem 0 0; }
.p-profile .s7 { font-size:1rem; }
.p-profile .s8 { margin-top:.8rem; }
.p-profile .s10 { width:100%; color:var(--brand-ink); border-color:var(--brand-soft); }

/* records */
.p-records .rec { display:flex; align-items:center; gap:.9rem; padding:1rem; border:1px solid var(--border); border-radius:16px; background:var(--panel); box-shadow:var(--shadow); margin-bottom:.7rem; }
.p-records .rec .thumb { width:48px; height:48px; border-radius:13px; flex:none; display:grid; place-items:center; font-size:1.5rem; background:var(--panel-2); }
.p-records .rec .body { flex:1; min-width:0; }
.p-records .rec .body b { display:block; font-size:1.1rem; font-weight:800; }
.p-records .rec .body span { font-size:.85rem; color:var(--muted); font-weight:600; }
.p-records .rec .big { text-align:right; flex:none; }
.p-records .rec .big b { font-size:1.6rem; font-weight:850; letter-spacing:-.02em; display:block; line-height:1; }
.p-records .rec .big span { font-size:.8rem; color:var(--muted); font-weight:650; }
.p-records details.more { margin-top:.9rem; }
/* Same affordance as every other disclosure on the app: without a marker the
   summary reads as inert grey text nobody taps. */
.p-records details.more > summary { cursor:pointer; list-style:none; font-weight:700; color:var(--muted); font-size:.9rem;
  display:flex; align-items:center; justify-content:space-between; gap:.5rem; }
.p-records details.more > summary::-webkit-details-marker { display:none; }
.p-records details.more > summary::after { content:"▸"; transition:transform .2s; }
.p-records details.more[open] > summary::after { transform:rotate(90deg); }
.p-records .s1 { margin-top:.7rem; }

/* routine_builder */
.p-routine_builder .weeks { display:flex; flex-wrap:wrap; gap:.35rem; align-items:center; margin:.25rem 0 .75rem; }
.p-routine_builder .wtab { padding:.4rem .8rem; border:1px solid var(--border); border-radius:var(--r-sm); text-decoration:none; color:var(--text); font-weight:600; }
.p-routine_builder .wtab.active { background:var(--brand-fill); color:var(--on-brand); border-color:var(--brand); }
.p-routine_builder .chips { display:flex; flex-wrap:wrap; gap:.35rem; margin:.25rem 0 .5rem; }
.p-routine_builder .chip { padding:.35rem .6rem; border:1px solid var(--border); border-radius:999px; text-decoration:none; color:var(--text); font-size:.85rem; }
.p-routine_builder .chip.active { background:var(--brand-fill); color:var(--on-brand); border-color:var(--brand); }
.p-routine_builder .chip.sub { border-style:dashed; }
/* Name on its own line, numbers underneath in equal columns. Sharing one
   line squeezed the name to nothing on a phone and sized every box to the
   shortest word. */
.p-routine_builder .ex { padding:.55rem 0; border-bottom:1px solid var(--border); }
.p-routine_builder .ex input[type=number] { width:100%; padding:.35rem .4rem; text-align:center; }
/* Multi-add: tick rows, fill their targets, submit once. */
.p-routine_builder .ex-pick { display:flex; align-items:center; gap:.5rem; margin:0; font-weight:400; cursor:pointer; min-width:0; }
/* What a member's body may not manage, folded rather than removed. Dimmed and
   still tickable: a prosthesis or an adaptation is the one thing the catalog
   cannot see, so this is a suggestion and never a locked door. Deliberately not
   greyed to the point of looking disabled -- these rows still work. */
.p-routine_builder .ex-hidden > summary { cursor:pointer; padding:.6rem 0; }
.p-routine_builder .ex-blocked { opacity:.72; }
.p-routine_builder .ex-why { display:block; margin-top:.1rem; color:var(--muted); }
/* A limb the member called "limited" rather than missing: offered as normal,
   with a hairline to say it was noticed. A judgement, not a bar. */
.p-routine_builder .ex-caution { border-left:2px solid var(--border); padding-left:.5rem; }
/* Picture on its own row under the name, aligned with the number fields.
   Big enough to recognise the movement, small enough for a long list. */
.p-routine_builder .ex-pic { margin:.45rem 0 0 1.65rem; }
/* Only a real photograph earns the space. The 4/3 block was being applied to
   the fallback glyph too, which blew one small icon up into an 11rem empty
   box on every exercise without a picture -- the tallest thing in the row,
   carrying nothing, on a screen whose whole job is scanning a long list. */
.p-routine_builder .ex-pic .ex-thumb {
  width:100%; max-width:11rem; height:auto; aspect-ratio:4/3; border-radius:12px; }
.p-routine_builder .ex-pic .ex-thumb img { width:100%; height:100%; aspect-ratio:4/3; object-fit:cover; }
/* The neutral tile, where one is still rendered: `_extile.html` falls back to
   it in the lists that want an even column. The builder's own rows no longer
   ask for it -- no picture, no tile -- so this is only reached by a nested
   plan list. */
.p-routine_builder .ex-pic .exglyph {
  width:5rem; max-width:5rem; height:auto; aspect-ratio:4/3; border-radius:10px; }
.p-routine_builder .ex-pic .exglyph svg { width:1.6rem; height:1.6rem; }
/* A checkbox you pick with a thumb, in the app's own colour rather than the
   browser's blue. The label is the target, so it carries the height. */
.p-routine_builder .ex-pick { min-height:2.75rem; }
.p-routine_builder .ex-pick input[type=checkbox] {
  width:1.4rem; height:1.4rem; flex:none; accent-color:var(--brand); cursor:pointer; }
.p-routine_builder .ex-fields {
  /* Always three columns, so a cardio row's two boxes line up with the
     strength rows above and below instead of stretching wider. */
  display:grid; grid-template-columns:repeat(3, minmax(0, 1fr)); gap:.4rem;
  /* indented to line up under the exercise name, not the checkbox */
  margin:.45rem 0 .1rem 1.65rem; max-width:22rem;
}
.p-routine_builder .exf { display:grid; gap:.1rem; margin:0; }
.p-routine_builder .exf span { font-size:.66rem; font-weight:800; text-transform:uppercase;
  letter-spacing:.04em; color:var(--muted); }
/* Sticks above the fixed bottom tab bar — at bottom:0 it would sit behind it
   and be unclickable at the end of a long catalog list. */
.p-routine_builder .addbar { display:flex; align-items:center; gap:.6rem; position:sticky; bottom:calc(4.6rem + env(safe-area-inset-bottom)); z-index:5; padding:.6rem 0 .2rem; background:var(--panel); }
.p-routine_builder .addbar button[disabled] { opacity:.45; cursor:not-allowed; }
.p-routine_builder .s10 { margin:-.2rem 0 .5rem; }
.p-routine_builder .added { border-color:var(--go); }
.p-routine_builder .inline { display:inline; }
.p-routine_builder .copy-day { margin-top:.9rem; border-top:1px solid var(--border); padding-top:.7rem; }
.p-routine_builder .copy-day > summary { cursor:pointer; font-weight:700; font-size:.92rem; }
.p-routine_builder .copy-days { display:flex; flex-wrap:wrap; gap:.5rem .9rem; margin:.5rem 0 .7rem; }
.p-routine_builder .copy-days .chk { display:flex; align-items:center; gap:.35rem; font-size:.9rem; }
.p-routine_builder .copy-days .chk input { width:1.1rem; height:1.1rem; }
.p-routine_builder .s2 { margin:0;padding:.4rem .7rem; }
.p-routine_builder .s3 { display:flex;align-items:baseline;justify-content:space-between;gap:1rem;flex-wrap:wrap; }
.p-routine_builder .s4 { margin-bottom:.25rem; }
.p-routine_builder .s6 { margin:0;padding:.3rem .6rem;font-size:.85rem; }
.p-routine_builder .s8 { display:inline; }
.p-routine_builder .s9 { margin:0;padding:.15rem .5rem;background:transparent;color:var(--muted);border:1px solid var(--border); }

/* routine_detail */
.p-routine_detail .week { cursor: grab; }
.p-routine_detail .week.dragging { opacity: .5; }
.p-routine_detail .wk-head { display:flex; align-items:baseline; justify-content:space-between; gap:.5rem; flex-wrap:wrap; }
.p-routine_detail .wk-ctrl { display:flex; align-items:center; gap:.3rem; flex-wrap:wrap; }
.p-routine_detail .wk-ctrl form { display:inline; margin:0; }
.p-routine_detail .wk-ctrl button, .p-routine_detail .wk-ctrl .btn { margin:0; padding:.35rem .6rem; font-size:.85rem; }
.p-routine_detail .ghost { background:transparent; color:var(--muted); border:1px solid var(--border); }
.p-routine_detail .handle { cursor:grab; color:var(--muted); padding:0 .3rem; }
.p-routine_detail .s2 { background:var(--brand-fill);color:var(--on-brand);border-color:var(--brand); }
.p-routine_detail .s3 { margin-top:.5rem; }
/* A native date field will not shrink below the width of "dd/mm/yyyy" plus
   its picker button, so a fixed two-column split pushed the second one off a
   narrow phone. Stack instead of overflowing. */
.p-routine_detail .s5 { display:grid; grid-template-columns:repeat(auto-fit, minmax(min(100%, 11rem), 1fr)); gap:.6rem; }
.p-routine_detail .s6 { color:var(--brand-ink); }
.p-routine_detail .s11 { font-size:.8rem;font-weight:400; }
.p-routine_detail .s12 { margin:.6rem 0 .2rem; }
/* Week overview: a tidy picture-and-name row. Numbered markers and a picture
   under every line turned a five-week summary into a scroll. */
.p-routine_detail .daylist { list-style:none; margin:.2rem 0 .1rem; padding:0; }
.p-routine_detail .daylist li { display:flex; align-items:center; gap:.6rem; padding:.28rem 0; }
.p-routine_detail .daylist li .ex-media, .p-routine_detail .daylist li .exglyph { flex:none; }
/* Landscape, not square: these illustrations are wide, and a 42px square crop
   of the middle of one is a smudge. */
.p-routine_detail .daylist .ex-thumb, .p-routine_detail .daylist .exglyph { width:4.25rem; height:3.2rem; }
.p-routine_detail .daylist li > span { min-width:0; }
.p-routine_detail .s14 { display:none; }

/* routines */
.p-routines .disclose > summary { cursor:pointer; list-style:none; font-weight:800; font-size:1.15rem; display:flex; align-items:center; justify-content:space-between; }
.p-routines .disclose > summary::-webkit-details-marker { display:none; }
.p-routines .disclose > summary .chev { color:var(--muted); transition:transform .2s; }
.p-routines .disclose[open] > summary .chev { transform:rotate(90deg); }

/* stats */
/* The period switch is three targets, not seven: it can afford to be bigger. */
.p-stats .switch .tab { padding:.6rem 1.1rem; }

.p-stats .tiles { display:grid; grid-template-columns:1fr 1fr; gap:.7rem; }
.p-stats .tile { border:1px solid color-mix(in srgb, var(--border) 45%, transparent); border-radius:var(--r); padding:var(--sp-4); background:var(--panel); box-shadow:var(--shadow); }
.p-stats .tile .ic { width:36px; height:36px; border-radius:50%; display:grid; place-items:center; margin-bottom:var(--sp-3); background:var(--panel-3); }
.p-stats .tile .ic svg { width:21px; height:21px; }
.p-stats .tile .big { font-family:var(--font-display); font-size:2rem; font-weight:800; display:block; line-height:1; letter-spacing:-.01em; font-variant-numeric:tabular-nums; }
.p-stats .tile .lbl { display:block; margin-top:var(--sp-2); color:var(--muted); font-family:var(--font-mono); font-size:.7rem; line-height:1rem; font-weight:600; letter-spacing:.1em; text-transform:uppercase; }

.p-stats .chart { display:flex; align-items:flex-end; gap:.5rem; height:10rem; margin-top:.6rem; }
.p-stats .bar { flex:1; display:flex; flex-direction:column; align-items:center; gap:.35rem; height:100%; justify-content:flex-end; }
.p-stats .bar .fill { width:100%; max-width:2.2rem; background:var(--panel-3); border-radius:var(--r-sm) var(--r-sm) 2px 2px; min-height:4px; transition:height .25s; }
/* The week you are in is the one that matters; the rest are context. */
.p-stats .bar:last-child .fill { background:linear-gradient(to top, color-mix(in srgb, var(--brand) 35%, transparent), var(--brand)); box-shadow:var(--glow-soft); }
.p-stats .bar:last-child .val { color:var(--brand-ink); }
.p-stats .bar .cap { font-family:var(--font-mono); font-size:.65rem; color:var(--muted); font-weight:600; letter-spacing:.06em; }
.p-stats .bar .val { font-family:var(--font-mono); font-size:.68rem; font-weight:700; font-variant-numeric:tabular-nums; }

.p-stats .bw { display:flex; align-items:center; justify-content:space-between; gap:1rem; }
.p-stats .bw .big { font-family:var(--font-display); font-size:2.2rem; font-weight:800; letter-spacing:-.01em; font-variant-numeric:tabular-nums; }

.p-stats .disclose > summary { cursor:pointer; list-style:none; font-weight:800; font-size:1.15rem; display:flex; align-items:center; justify-content:space-between; }
.p-stats .disclose > summary::-webkit-details-marker { display:none; }
.p-stats .disclose > summary .chev { color:var(--muted); transition:transform .2s; }
.p-stats .disclose[open] > summary .chev { transform:rotate(90deg); }
.p-stats .disclose { margin-bottom:1rem; }

/* Muscle balance rows. Unscoped: the stats screen reads them back from logged
   sets and the routine builder reads them forward from a plan, and the two are
   only comparable if they are drawn identically. */
.mgrp { font-family:var(--font-mono); font-size:.72rem; font-weight:600; text-transform:uppercase; letter-spacing:.1em; color:var(--muted); margin:var(--sp-4) 0 var(--sp-1); }
.mgrp:first-of-type { margin-top:.3rem; }
.mrow { display:grid; grid-template-columns:6rem 1fr 4.5rem; align-items:center; gap:.6rem; margin:.45rem 0; }
.mbar { height:.5rem; border-radius:var(--r-pill); background:var(--panel-3); overflow:hidden; }
.mbar > span { display:block; height:100%; border-radius:var(--r-pill); }
.mtag { font-family:var(--font-mono); font-size:.68rem; color:var(--muted); font-weight:600; letter-spacing:.04em; }
.s-ok > span { background:var(--go); } .s-low > span { background:var(--wait); }
.s-high > span { background:var(--brand); } .s-none > span { background:var(--muted); }
/* Shared by the stats page and the health page: both draw a grid of metrics,
   each with a sparkline of its own history. */
.p-stats .spark, .p-health .spark { display:flex; align-items:flex-end; gap:2px; height:2.6rem; }
.p-stats .spark > div, .p-health .spark > div { flex:1; background:var(--brand); border-radius:3px 3px 0 0; min-height:2px; opacity:.85; }
.p-stats .trendgrid { display:grid; grid-template-columns:repeat(auto-fit,minmax(11rem,1fr)); gap:1rem; }
.p-stats .delta-up, .p-health .delta-up { color:var(--go-ink); }
.p-stats .delta-down, .p-health .delta-down { color:var(--brand-ink); }

/* A night's sleep as its parts, stacked. Percentages come from the server so
   the bar is pure layout — the widths already sum to 100. */
/* A night is a composition, not a bar: every one fills the box and is split
   into its stages, so the row stretches rather than aligning to the bottom.
   `flex-end` sized each night to its content, its content was four spans with
   percentage heights, and a percentage of nothing is nothing — the chart
   rendered blank on every page that has ever shown it. */
.p-health .nights { display:flex; align-items:stretch; gap:4px; height:7rem; }
.p-health .night { flex:1; height:100%; display:flex; flex-direction:column; border-radius:4px; overflow:hidden; min-width:0; }
.p-health .night > span { display:block; width:100%; }
/* Four bands of one quantity, encoded by depth rather than by status. REM
   used to take `--go`, which the default look aliases to the brand — so a
   legend naming four stages drew two of them in the same lime, in the one
   chart whose entire job is showing a proportion. Sleep stages are not
   successes, and borrowing a semantic token left the chart at the mercy of
   what any theme did to it. */
.p-health .st-deep { background:var(--brand); }
.p-health .st-rem { background:var(--brand); opacity:.6; }
.p-health .st-light { background:var(--muted); opacity:.55; }
.p-health .st-awake { background:var(--wait); opacity:.7; }
.p-health .legend { display:flex; flex-wrap:wrap; gap:.75rem; }
.p-health .legend span { display:inline-flex; align-items:center; gap:.35rem; }
.p-health .legend i { width:.7rem; height:.7rem; border-radius:2px; display:inline-block; }
/* Spacing steps, matching the stats page's scale so the two read alike. */
.p-health .s7 { width:100%; margin-top:.9rem; }
.p-health .mgrid { display:flex; flex-direction:column; }
.p-health .mrow { display:flex; align-items:center; gap:.9rem; padding:.75rem 0; border-top:1px solid var(--border); }
.p-health .mrow:first-child { border-top:0; padding-top:.2rem; }
.p-health .mfig { flex:1; min-width:0; }
.p-health .mrow .spark { flex:0 0 6rem; width:6rem; height:1.9rem; }
.p-health .delta-flat { color:var(--muted); }

.p-stats .s1 { margin-bottom:1rem; }
/* Tile icons carry meaning, so they keep the semantic colours through a skin
   change: green = done, amber = watch this, brand = the app's own accent. */
.ic-go { background:var(--go-soft);color:var(--go-ink); }
.ic-brand { background:var(--brand-soft);color:var(--brand-ink); }
.ic-wait { background:var(--wait-soft);color:var(--wait-ink); }
.p-stats .s5 { display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.5rem;margin-bottom:.3rem; }
.p-stats .s7 { width:100%;margin-top:.9rem; }
.p-stats .s8 { margin-top:1.2rem; }
.p-stats .s10 { font-weight:400; }
.p-stats .s12 { font-size:1.4rem;color:var(--text); }
.p-stats .s13 { margin:0 0 .6rem; }
.p-stats .s15 { font-size:.95rem; }


/* ---------- groups (my groups, group detail, join) ---------- */
.p-groups .gp-sub { margin:-.4rem 0 1rem; }
.p-groups .gp-top { display:flex; align-items:baseline; justify-content:space-between; }
.p-groups .gp-card { padding:.9rem 1rem; }
.p-groups .gp-row { display:flex; align-items:center; justify-content:space-between; gap:.8rem; }
.p-groups .gp-name { font-weight:800; font-size:1.05rem; text-decoration:none; color:var(--text); }
.p-groups .gp-desc { margin:.2rem 0; }
.p-groups .gp-h2 { font-size:1.05rem; margin:0 0 .3rem; }
.p-groups .gp-summary { cursor:pointer; font-weight:700; }
.p-groups .gp-form { display:flex; flex-direction:column; gap:.2rem; margin-top:.6rem; }
.p-groups .gp-warn { border-color:var(--wait-soft); }
.p-groups .gp-link { width:100%; font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:.82rem; margin:.4rem 0 .6rem; }
.p-groups .gp-member { display:flex; align-items:center; justify-content:space-between; gap:.6rem; padding:.5rem 0; border-bottom:1px solid var(--border); }
.p-groups .gp-member:last-of-type { border-bottom:0; }
.p-groups .gp-member form { margin:0; }
.p-groups .gp-member button { margin:0; padding:.35rem .7rem; font-size:.85rem; }
.p-groups .gp-actions { display:flex; gap:.4rem; }
.p-groups .gp-note { margin-top:.6rem; }
/* Roomier than the member-facing 40rem column — this is an admin screen. */
@media (min-width: 64rem) { .p-manage_members main { max-width: 60rem; } }
.p-manage_members .s1 { font-size:1rem;font-weight:600; }
.p-manage_members .gp-sub { margin:-.4rem 0 1rem; }
.p-manage_members form { margin:0; }
.p-manage_members button { margin:0; padding:.35rem .7rem; font-size:.85rem; }

/* Per-set save: the button only appears once a row is edited. */
.p-log_today .savset { margin:0; padding:.25rem .6rem; min-height:0; font-size:.8rem; border-radius:var(--r-sm); }
/* A planned warm-up, stated on the row. Quiet: it labels the set, it is not
   a control — the plan decides, not the member. */
.warm-tag { font-size:.68rem; font-weight:800; text-transform:uppercase; letter-spacing:.04em;
  color:var(--wait-ink); background:var(--wait-soft); border-radius:6px; padding:.05rem .35rem;
  vertical-align:middle; margin-left:.25rem; }
.p-log_today .plan-preview { margin:.4rem 0 0; padding-left:1.3rem; }
.p-log_today .plan-preview li { margin:.25rem 0; }
/* The row's single status chip. It swaps text/tone in place — just-saved and
   new-PB show for a beat, then settle back to "Done" — so a saved set never
   carries two badges saying the same thing. */
.p-log_today .setstate { white-space:nowrap; transition:background .2s, color .2s; }
.pill.err { background:color-mix(in srgb, var(--error) 14%, transparent); color:var(--error); }
.p-log_today .setstate.pop { animation:setpop .32s cubic-bezier(.2,1.5,.4,1); }
@keyframes setpop {
  0%   { transform:scale(.86); opacity:.5; }
  60%  { transform:scale(1.06); opacity:1; }
  100% { transform:scale(1); }
}

/* ---- Exercise illustration + how-to clip on the log page ----
   The tile sits inline with the exercise name and IS the play control when a
   how-to clip exists (a separate button just duplicated it). Exercises with no
   picture get a neutral glyph so every row keeps the same shape. */
.ex-thumb { position:relative; display:grid; place-items:center; flex:none;
  width:2.6rem; height:2.6rem; padding:0; margin:0; overflow:hidden;
  border:1px solid var(--border); border-radius:12px; background:var(--panel-2);
  box-shadow:none; min-height:0; color:var(--muted); }
.ex-thumb img { width:100%; height:100%; object-fit:cover; display:block; }
.exglyph { display:grid; place-items:center; width:2.6rem; height:2.6rem;
  border:1px solid var(--border); border-radius:12px; background:var(--panel-2); color:var(--muted); }
.exglyph svg { width:1.5rem; height:1.5rem; display:block; }
button.ex-thumb { cursor:pointer; }
button.ex-thumb:hover { border-color:var(--brand); }
/* Thumb + picture/clip chips on one inline row. */
.ex-media { display:flex; align-items:center; gap:.45rem; flex-wrap:wrap; }
.p-log_today .ex-media { margin:0 0 .7rem; }
/* Full-width illustration above an exercise's sets — see `_exhero.html`. The
   picture is the button: tapping it opens the full-size version. */
.exhero { position:relative; margin:0 0 .8rem; }
/* Capped on a wide screen so it stays a picture rather than a poster. */
.exhero-img { display:block; width:100%; max-width:30rem; padding:0; margin:0; overflow:hidden;
  border:1px solid var(--border); border-radius:14px; background:var(--panel-2);
  box-shadow:none; min-height:0; cursor:zoom-in; }
/* The picture fills the frame edge to edge — no bands, whatever shape it was
   uploaded in — and 4:3 gives it more height than the old 3:2 letterbox. Tap
   opens the uncropped original. */
.exhero-img img { display:block; width:100%; aspect-ratio:4/3; object-fit:cover; }
.exhero-img:hover { border-color:var(--brand); }
/* A clip's play control sits on the picture rather than beside it. */
.exhero .playbtn { position:absolute; right:.55rem; bottom:.55rem; width:2.5rem; height:2.5rem;
  background:rgba(0,0,0,.55); border-color:transparent; color:#fff; }
.exhero .playbtn:hover { background:var(--brand-fill); color:var(--on-brand); border-color:transparent; }

/* Play control for a clip — an icon, no label: the triangle is the word. */
.playbtn { display:inline-grid; place-items:center; margin:0; padding:0;
  width:2.1rem; height:2.1rem; min-height:0; border-radius:50%;
  border:1px solid var(--border); background:var(--panel-2); color:var(--text);
  box-shadow:none; cursor:pointer; }
.playbtn svg { width:1rem; height:1rem; }
.playbtn:hover { border-color:var(--brand); color:var(--brand); }


/* Video modal: wider than the confirm dialog, 16:9 player. */
.vid-card { max-width: 44rem; }
.vid-frame { position:relative; width:100%; aspect-ratio:16/9; background:#000; border-radius:12px; overflow:hidden; margin:.2rem 0 .4rem; }
.vid-frame > iframe, .vid-frame > video { position:absolute; inset:0; width:100%; height:100%; border:0; display:block; }

/* ---- Supersets: paired exercises logged round by round ---- */
.p-log_today .panel.superset { border-color: color-mix(in srgb, var(--brand) 45%, var(--border)); }
.p-log_today .ss-head { display:flex; align-items:baseline; justify-content:space-between; gap:.6rem; margin-bottom:.5rem; }
.p-log_today .ss-head h2 { margin:0; }
.p-log_today .ss-legend { display:flex; flex-direction:column; gap:.4rem; margin-bottom:.8rem; }
.p-log_today .ss-item { display:flex; align-items:center; gap:.55rem; }
.p-log_today .ss-name { font-weight:700; font-size:.95rem; }
.p-log_today .ss-num { display:inline-grid; place-items:center; width:1.3rem; height:1.3rem; border-radius:50%;
  background:var(--brand-fill); color:var(--on-brand); font-size:.75rem; margin-right:.15rem; }
.p-log_today .ss-round { border:1px solid var(--border); border-radius:14px; padding:.6rem .65rem; margin-bottom:.7rem; background:var(--panel-2); }
.p-log_today .ss-round-head { font-size:.8rem; font-weight:800; text-transform:uppercase; letter-spacing:.04em; color:var(--muted); margin-bottom:.4rem; }
.p-log_today .ss-entry-name { font-size:.85rem; font-weight:700; color:var(--muted); margin:.2rem 0 -.2rem; }
.p-log_today .ss-round .setrow { background:var(--panel); }

/* Builder: pair / unpair controls on the day plan */
/* A plan row: name line (tick + name + remove), then its picture below. */
.p-routine_builder ol li { display:flex; align-items:center; gap:.55rem; flex-wrap:wrap; }
.p-routine_builder ol li .ex-pic { flex-basis:100%; margin:.1rem 0 .3rem 1.65rem; }
.p-routine_builder .ss-pick { display:flex; align-items:center; gap:.5rem; margin:0; font-weight:400; cursor:pointer; flex:1; min-width:0; }
.p-routine_builder .ss-pick input[type=checkbox] { width:1.15rem; height:1.15rem; flex:none; }
.p-routine_builder .ss-tag { display:inline-block; font-size:.7rem; font-weight:800; text-transform:uppercase;
  letter-spacing:.03em; color:var(--brand); border:1px solid var(--brand); border-radius:6px; padding:0 .3rem; vertical-align:middle; }
.p-routine_builder .ss-actions { display:flex; gap:.5rem; flex-wrap:wrap; margin:.5rem 0 .2rem; }
.p-routine_builder .ss-actions button { margin:0; padding:.4rem .8rem; font-size:.9rem; min-height:0; }
.p-routine_builder .ss-row-actions { flex:none; }
.p-routine_builder .ss-warn { color:var(--wait-ink); }
/* An image in the viewer keeps its own aspect ratio instead of 16:9 letterbox. */
.vid-frame.is-image { aspect-ratio: auto; max-height: 70vh; background: transparent; }
.vid-frame.is-image > img { position: static; width: 100%; height: auto; max-height: 70vh; object-fit: contain; display: block; }

/* Ad-hoc sets logged outside the day's plan — same editing affordances,
   visually marked so it's clear they weren't scheduled. */
.p-log_today .panel.extra { border-style:dashed; }
.p-log_today .extratag { display:inline-block; font-size:.68rem; font-weight:800; text-transform:uppercase;
  letter-spacing:.04em; color:var(--muted); border:1px solid var(--border); border-radius:6px;
  padding:0 .35rem; vertical-align:middle; }

/* Offline outbox: the sync bar. (A queued row shows it in its status chip.) */
.syncbar { position:fixed; left:.6rem; right:.6rem; z-index:70;
  bottom:calc(4.9rem + env(safe-area-inset-bottom));
  display:flex; align-items:center; justify-content:space-between; gap:.6rem;
  padding:.5rem .7rem; border-radius:14px;
  background:var(--wait-soft); color:var(--wait-ink);
  border:1px solid var(--wait); box-shadow:0 10px 30px -12px rgba(0,0,0,.5); }
.syncbar .syncbody { display:flex; flex-direction:column; gap:.1rem; min-width:0; }
.syncbar .syncmsg { font-size:.85rem; font-weight:800; }
.syncbar .syncnote { font-size:.74rem; font-weight:600; opacity:.85; }
.syncbar.is-failed { background:var(--error); color:#1b1b1f; border-color:var(--error); }
.syncbar.is-failed button { background:#1b1b1f; color:var(--error); box-shadow:none; }
.syncbar button[disabled] { opacity:.6; }
.syncbar button { flex:none; margin:0; padding:.3rem .7rem; min-height:0; font-size:.82rem;
  background:var(--wait); color:#231f26; border:0; }

/* Phones: a 5-column admin table cannot fit, and scrolling it sideways hides
   the actions. Each row becomes a two-line card instead:
     line 1 — name .................. Edit / Delete
     line 2 — media strip · MET      */
@media (max-width: 48rem) {
  .admin-table, .admin-table tbody { display:block; }
  .admin-table thead { display:none; }
  .admin-table tr {
    display:grid; grid-template-columns:auto 1fr auto; align-items:center;
    gap:.35rem .55rem; padding:.6rem 0;
    grid-template-areas:
      "name  name actions"
      "media met  met";
  }
  .admin-table td { display:block; border:0; padding:0; min-width:0; }
  .admin-table .cname { grid-area:name; }
  .admin-table .cactions { grid-area:actions; }
  /* Overrides the desktop `width:1%` media column set in the page block. */
  .admin-table td.cmedia { grid-area:media; width:auto; white-space:normal; }
  .admin-table .cmet, .admin-table .ctier { grid-area:met; color:var(--muted); font-size:.85rem; }
  .admin-table .cmet::before { content:"MET "; font-size:.72rem; font-weight:800; letter-spacing:.03em; }
  .admin-table tbody tr + tr { border-top:1px solid var(--border); }
}

/* ---- Extra looks -------------------------------------------------------
   Eight identities beyond Kinetic and Rose. Each is derived from a single
   hue rather than hand-picked, and each of the sixteen palettes below was
   checked against WCAG before it was written: the accent clears 3:1 on its
   own ground (1.4.11, UI components), the label on a filled button clears
   4.5:1, and the accent used as text clears 4.5:1. `look_palettes_are_legible`
   re-checks all of it from the stylesheet, so a hand edit here cannot quietly
   drop below the line.

   Dark palettes follow the platform guidance rather than inverting the light
   ones: dark greys instead of black, accents around twenty points less
   saturated, and no pure white for text.
   ---------------------------------------------------------------------- */
/* Tide — rounded, derived from one hue and verified for contrast.
   Surfaces are neutral greys tinted toward the hue: enough that the theme
   is felt on every surface, faint enough not to fight the content. */
:root[data-look="tide"] {
  --brand:#39aec6; --brand-fill:var(--brand); --on-brand:#0d0f12;
  --brand-soft:color-mix(in srgb, var(--brand) 12%, transparent);
  --brand-ink:#3cc2dd;
  --bg:#151b1c; --panel:#1d2426; --panel-2:#22292b;
  --panel-3:#2d3436; --panel-low:#0f1516; --bright:#3a4244;
  --text:#e3e4e4; --muted:#93a7ab; --border:#394b4f; --outline:#718386;
  --chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%2393a7ab' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  color-scheme:dark;
}
/* Tide on paper. */
:root[data-look="tide"][data-theme="light"] {
  --brand:#3299ae; --brand-fill:var(--brand); --on-brand:#0d0f12;
  --brand-soft:color-mix(in srgb, var(--brand) 12%, transparent);
  --brand-ink:#246c7b;
  --bg:#f3f7f8; --panel:#ffffff; --panel-2:#ecf0f1;
  --panel-3:#dfe5e7; --panel-low:#eff3f4; --bright:#d6dddf;
  --text:#1c2324; --muted:#556568; --border:#c5d2d4; --outline:#808f92;
  --chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23556568' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  color-scheme:light;
}
/* Ember — rounded, derived from one hue and verified for contrast.
   Surfaces are neutral greys tinted toward the hue: enough that the theme
   is felt on every surface, faint enough not to fight the content. */
:root[data-look="ember"] {
  --brand:#df7020; --brand-fill:var(--brand); --on-brand:#0d0f12;
  --brand-soft:color-mix(in srgb, var(--brand) 12%, transparent);
  --brand-ink:#f47b25;
  --bg:#1c1815; --panel:#27201c; --panel-2:#2d2520;
  --panel-3:#3a2e28; --panel-low:#16120f; --bright:#4a3b35;
  --text:#e7e3e0; --muted:#ae9c90; --border:#514136; --outline:#8a796e;
  --chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23ae9c90' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  color-scheme:dark;
}
/* Ember on paper. */
:root[data-look="ember"][data-theme="light"] {
  --brand:#db6d1f; --brand-fill:var(--brand); --on-brand:#0d0f12;
  --brand-soft:color-mix(in srgb, var(--brand) 12%, transparent);
  --brand-ink:#9c4e16;
  --bg:#f8f5f3; --panel:#ffffff; --panel-2:#f1eeec;
  --panel-3:#e7e2df; --panel-low:#f4f1ef; --bright:#dfd9d6;
  --text:#241f1c; --muted:#685d55; --border:#d4cbc5; --outline:#928780;
  --chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23685d55' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  color-scheme:light;
}
/* Iris — rounded, derived from one hue and verified for contrast.
   Surfaces are neutral greys tinted toward the hue: enough that the theme
   is felt on every surface, faint enough not to fight the content. */
:root[data-look="iris"] {
  --brand:#7c45c9; --brand-fill:var(--brand); --on-brand:#ffffff;
  --brand-soft:color-mix(in srgb, var(--brand) 12%, transparent);
  --brand-ink:#ac81e9;
  --bg:#18151c; --panel:#211c26; --panel-2:#27202b;
  --panel-3:#322836; --panel-low:#120f16; --bright:#413544;
  --text:#e4e1e4; --muted:#9e90ab; --border:#43364f; --outline:#7b6f86;
  --chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%239e90ab' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  color-scheme:dark;
}
/* Iris on paper. */
:root[data-look="iris"][data-theme="light"] {
  --brand:#7439c6; --brand-fill:var(--brand); --on-brand:#ffffff;
  --brand-soft:color-mix(in srgb, var(--brand) 12%, transparent);
  --brand-ink:#6834b2;
  --bg:#f5f3f8; --panel:#ffffff; --panel-2:#eeecf1;
  --panel-3:#e2dfe7; --panel-low:#f1eff4; --bright:#d9d6df;
  --text:#1f1c24; --muted:#5d5568; --border:#cbc5d4; --outline:#878092;
  --chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%235d5568' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  color-scheme:light;
}
/* Moss — rounded, derived from one hue and verified for contrast.
   Surfaces are neutral greys tinted toward the hue: enough that the theme
   is felt on every surface, faint enough not to fight the content. */
:root[data-look="moss"] {
  --brand:#46b96c; --brand-fill:var(--brand); --on-brand:#0d0f12;
  --brand-soft:color-mix(in srgb, var(--brand) 12%, transparent);
  --brand-ink:#47d175;
  --bg:#151c17; --panel:#1d2620; --panel-2:#222b24;
  --panel-3:#2d362d; --panel-low:#0f1612; --bright:#3a443a;
  --text:#e3e5e1; --muted:#93ac99; --border:#394f3f; --outline:#718776;
  --chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%2393ac99' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  color-scheme:dark;
}
/* Moss on paper. */
:root[data-look="moss"][data-theme="light"] {
  --brand:#3c9f5d; --brand-fill:var(--brand); --on-brand:#0d0f12;
  --brand-soft:color-mix(in srgb, var(--brand) 12%, transparent);
  --brand-ink:#2b7343;
  --bg:#f3f8f5; --panel:#ffffff; --panel-2:#ecf1ee;
  --panel-3:#dfe7e2; --panel-low:#eff4f1; --bright:#d6dfd9;
  --text:#1c241f; --muted:#55685b; --border:#c5d4ca; --outline:#809286;
  --chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%2355685b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  color-scheme:light;
}
/* Cobalt — rounded, derived from one hue and verified for contrast.
   Surfaces are neutral greys tinted toward the hue: enough that the theme
   is felt on every surface, faint enough not to fight the content. */
:root[data-look="cobalt"] {
  --brand:#2d72d2; --brand-fill:var(--brand); --on-brand:#ffffff;
  --brand-soft:color-mix(in srgb, var(--brand) 12%, transparent);
  --brand-ink:#5e9aed;
  --bg:#15181c; --panel:#1d2026; --panel-2:#22252b;
  --panel-3:#2d2e36; --panel-low:#0f1216; --bright:#3a3b44;
  --text:#e3e3e4; --muted:#939cab; --border:#39414f; --outline:#717986;
  --chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23939cab' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  color-scheme:dark;
}
/* Cobalt on paper. */
:root[data-look="cobalt"][data-theme="light"] {
  --brand:#2d72d2; --brand-fill:var(--brand); --on-brand:#ffffff;
  --brand-soft:color-mix(in srgb, var(--brand) 12%, transparent);
  --brand-ink:#2662b5;
  --bg:#f3f5f8; --panel:#ffffff; --panel-2:#eceef1;
  --panel-3:#dfe2e7; --panel-low:#eff1f4; --bright:#d6d9df;
  --text:#1c1f24; --muted:#555d68; --border:#c5cbd4; --outline:#808792;
  --chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23555d68' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  color-scheme:light;
}
/* Solar — rounded, derived from one hue and verified for contrast.
   Surfaces are neutral greys tinted toward the hue: enough that the theme
   is felt on every surface, faint enough not to fight the content. */
:root[data-look="solar"] {
  --brand:#e6b31a; --brand-fill:var(--brand); --on-brand:#0d0f12;
  --brand-soft:color-mix(in srgb, var(--brand) 12%, transparent);
  --brand-ink:#f9c31f;
  --bg:#1c1a15; --panel:#27231c; --panel-2:#2d2820;
  --panel-3:#3a3228; --panel-low:#16140f; --bright:#4a4035;
  --text:#e7e4e0; --muted:#aea590; --border:#514936; --outline:#8a816e;
  --chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23aea590' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  color-scheme:dark;
}
/* Solar on paper. */
:root[data-look="solar"][data-theme="light"] {
  --brand:#ae8813; --brand-fill:var(--brand); --on-brand:#0d0f12;
  --brand-soft:color-mix(in srgb, var(--brand) 12%, transparent);
  --brand-ink:#7c600e;
  --bg:#f8f7f3; --panel:#ffffff; --panel-2:#f1f0ec;
  --panel-3:#e7e5df; --panel-low:#f4f3ef; --bright:#dfddd6;
  --text:#24221c; --muted:#686355; --border:#d4d0c5; --outline:#928d80;
  --chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23686355' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  color-scheme:light;
}
/* Plum — rounded, derived from one hue and verified for contrast.
   Surfaces are neutral greys tinted toward the hue: enough that the theme
   is felt on every surface, faint enough not to fight the content. */
:root[data-look="plum"] {
  --brand:#c63997; --brand-fill:var(--brand); --on-brand:#ffffff;
  --brand-soft:color-mix(in srgb, var(--brand) 12%, transparent);
  --brand-ink:#e467bb;
  --bg:#1c151a; --panel:#271c23; --panel-2:#2d2028;
  --panel-3:#3a2831; --panel-low:#160f14; --bright:#4a353f;
  --text:#e7e1e3; --muted:#ae90a2; --border:#513647; --outline:#8a6f7e;
  --chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23ae90a2' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  color-scheme:dark;
}
/* Plum on paper. */
:root[data-look="plum"][data-theme="light"] {
  --brand:#c63997; --brand-fill:var(--brand); --on-brand:#ffffff;
  --brand-soft:color-mix(in srgb, var(--brand) 12%, transparent);
  --brand-ink:#aa3182;
  --bg:#f8f3f6; --panel:#ffffff; --panel-2:#f1ecef;
  --panel-3:#e7dfe4; --panel-low:#f4eff2; --bright:#dfd6dc;
  --text:#241c21; --muted:#685562; --border:#d4c5cf; --outline:#92808c;
  --chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23685562' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  color-scheme:light;
}
/* Carbon — rounded, derived from one hue and verified for contrast.
   Surfaces are neutral greys tinted toward the hue: enough that the theme
   is felt on every surface, faint enough not to fight the content. */
:root[data-look="carbon"] {
  --brand:#737d91; --brand-fill:var(--brand); --on-brand:#0d0f12;
  --brand-soft:color-mix(in srgb, var(--brand) 12%, transparent);
  --brand-ink:#8798ba;
  --bg:#15171c; --panel:#1d2026; --panel-2:#22242b;
  --panel-3:#2d2d36; --panel-low:#0f1216; --bright:#3a3a44;
  --text:#e3e2e4; --muted:#939aab; --border:#393f4f; --outline:#717786;
  --chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23939aab' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  color-scheme:dark;
}
/* Carbon on paper. */
:root[data-look="carbon"][data-theme="light"] {
  --brand:#737d91; --brand-fill:var(--brand); --on-brand:#0d0f12;
  --brand-soft:color-mix(in srgb, var(--brand) 12%, transparent);
  --brand-ink:#5c6475;
  --bg:#f3f5f8; --panel:#ffffff; --panel-2:#eceef1;
  --panel-3:#dfe2e7; --panel-low:#eff1f4; --bright:#d6d9df;
  --text:#1c1f24; --muted:#555b68; --border:#c5cad4; --outline:#808692;
  --chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23555b68' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  color-scheme:light;
}
