/* Default theme = Dark. Themes are selected via <html data-theme> (see
   js/theme.js); each non-default theme overrides these CSS variables below. */
:root {
  color-scheme: dark;
  --bg: #0f172a;
  --surface: #1e293b;
  --surface-2: #334155;
  --border: #475569;
  /* Default accent = charcoal (js/accent.js DEFAULT_ACCENT). The live accent is applied
     via [data-accent] (pre-paint + the rules near the foot of this file); these are the
     CSS-level FALLBACKS for when no accent attr is applied (degraded / stale-partial
     deploy) — kept charcoal so nothing ever flashes the old blue. */
  --primary: #374151;
  --primary-dark: #1f2937;
  /* Secondary brand accents — a second + third user-pickable accent (js/accent.js,
     data-secondary / data-tertiary). Defaults form a teal + amber trio with the
     blue primary; each has a darker hover/active shade. */
  --secondary: #14b8a6;
  --secondary-dark: #0d9488;
  --tertiary: #f59e0b;
  --tertiary-dark: #d97706;
  --text: #f8fafc;
  --text-secondary: #94a3b8;
  --danger: #ef4444;
  --success: #22c55e;
  --warning: #f59e0b;
  --radius: 12px;
  --radius-sm: 8px;
  /* Full-screen overlay scrim (rest timer / stopwatch) + the faint countdown
     ring track + the timer phase / pace colours. Themed so the timer and
     stopwatch follow the app's dark / light / high-contrast style. */
  --scrim: rgba(0, 0, 0, 0.78);
  --ring-track: rgba(255, 255, 255, 0.08);
  --phase-red: #ef4444;
  --phase-orange: #f97316;
  --phase-yellow: #eab308;
  --phase-green: #22c55e;
}

/* Light theme — light surfaces, dark text, accessible contrast. The --primary
   fallback stays charcoal (see :root note) so an unstyled/stale load never flashes
   blue; the live accent still overrides via [data-accent]. */
[data-theme="light"] {
  color-scheme: light;
  --bg: #f1f5f9;
  --surface: #ffffff;
  --surface-2: #e2e8f0;
  --border: #cbd5e1;
  --primary: #374151;
  --primary-dark: #1f2937;
  --secondary: #0d9488;
  --secondary-dark: #0f766e;
  --tertiary: #d97706;
  --tertiary-dark: #b45309;
  --text: #0f172a;
  --text-secondary: #475569;
  --danger: #dc2626;
  --success: #16a34a;
  --warning: #d97706;
  /* Light frosted scrim + darker ring track / phase colours that stay legible
     on a light backdrop. */
  --scrim: rgba(241, 245, 249, 0.9);
  --ring-track: rgba(15, 23, 42, 0.12);
  --phase-red: #dc2626;
  --phase-orange: #ea580c;
  --phase-yellow: #a16207;
  --phase-green: #16a34a;
}

/* High contrast — standalone accessibility theme: near-black text on white,
   black borders, and saturated dark accents that hold ≥7:1 contrast (WCAG AAA)
   against white, so white-on-accent buttons stay legible too. */
[data-theme="high-contrast"] {
  color-scheme: light;
  --bg: #ffffff;
  --surface: #ffffff;
  --surface-2: #e6e6e6;
  --border: #000000;
  --primary: #0033cc;
  --primary-dark: #002299;
  /* AAA-tuned secondary accents that hold ≥7:1 on white (white-on-accent stays
     legible). High contrast pins these — the named-accent overrides below are
     scoped to exclude it, exactly like --primary. */
  --secondary: #006b6b;
  --secondary-dark: #004d4d;
  --tertiary: #8a4b00;
  --tertiary-dark: #6b3a00;
  --text: #000000;
  --text-secondary: #1a1a1a;
  --danger: #b00000;
  --success: #006b2d;
  --warning: #6b4500;
  /* Near-opaque white scrim (blur/translucency erodes contrast) + bold black
     track + saturated dark phase colours that hold AAA contrast on white. */
  --scrim: rgba(255, 255, 255, 0.97);
  --ring-track: #000000;
  --phase-red: #b00000;
  --phase-orange: #8a4b00;
  --phase-yellow: #6b5800;
  --phase-green: #006b2d;
}
/* On white surfaces, cards/inputs lose their edges without a visible border,
   and focus needs to be unmistakable. */
[data-theme="high-contrast"] .card,
[data-theme="high-contrast"] .section-card,
[data-theme="high-contrast"] .lift-card,
[data-theme="high-contrast"] .workout-card {
  border: 1px solid var(--border);
}
[data-theme="high-contrast"] .tab-bar { border-top: 2px solid var(--border); }
[data-theme="high-contrast"] :focus-visible { outline: 3px solid var(--primary); outline-offset: 2px; }

/* Noir — true-black OLED: pure-black surfaces, near-white text, and a MONOCHROME
   white accent (pinned below so the accent picker can't tint it). Semantic colours
   (danger/success/warning) and phase colours stay as the dark theme so errors and
   the timer ramp still read. (feat_theme_noir) */
[data-theme="noir"] {
  color-scheme: dark;
  --bg: #000000;
  --surface: #0a0a0a;
  --surface-2: #161616;
  --border: #1c1c1c;
  --primary: #ffffff;
  --primary-dark: #e0e0e0;
  /* Secondary/tertiary kept as dark greys so the monochrome look holds AND
     white-on-accent (.btn-secondary etc.) stays legible without overrides. */
  --secondary: #404040;
  --secondary-dark: #525252;
  --tertiary: #525252;
  --tertiary-dark: #404040;
  --text: #f5f5f5;
  --text-secondary: #8a8a8a;
  --danger: #ef4444;
  --success: #22c55e;
  --warning: #f59e0b;
  --scrim: rgba(0, 0, 0, 0.92);
  --ring-track: rgba(255, 255, 255, 0.1);
  --phase-red: #ef4444;
  --phase-orange: #f97316;
  --phase-yellow: #eab308;
  --phase-green: #22c55e;
}
/* On pure black, surfaces sit a hair off the background — a hairline border gives
   cards/inputs a definite edge. */
[data-theme="noir"] .card,
[data-theme="noir"] .section-card,
[data-theme="noir"] .lift-card,
[data-theme="noir"] .workout-card { border: 1px solid var(--border); }
/* A surface/outline button on pure black is nearly invisible (black-on-black) —
   give every button a slim border so its edge reads. A hair lighter than the card
   --border (#1c1c1c), which is too faint on the smaller button footprint.
   (fix_noir_theme_button_border) */
[data-theme="noir"] .btn { border: 1px solid #333; }
/* The white accent means anything FILLED with --primary needs dark text (the app
   otherwise hardcodes white-on-primary). Scoped to the prominent primary-filled
   controls; everything else uses --primary only for borders/dots/rings. */
[data-theme="noir"] .btn-primary,
[data-theme="noir"] .accent-custom-wrap.is-active,
[data-theme="noir"] .log-pill,
[data-theme="noir"] .settings-tab.active,
[data-theme="noir"] .hub-seg-opt.active,
[data-theme="noir"] .data-migration-banner,
[data-theme="noir"] .section-card[open] > summary::after,
[data-theme="noir"] .accent-gradient { color: #000; }
/* Noir is fully black & white: desaturate EVERYTHING the palette can't reach —
   coloured emoji, SVG icons with hardcoded fills (e.g. the amber flame), photos,
   charts, semantic colours. A grayscale filter on the document ROOT does this in
   one stroke; per the CSS spec the root element is EXEMPT from filter creating a
   containing block, so position:fixed overlays (rest timer, banners) are NOT
   broken. (feat_theme_noir — full monochrome) */
html[data-theme="noir"] { filter: grayscale(1); }

/* Blanc — the inverse of Noir: paper-white surfaces, near-black text, and a
   MONOCHROME black accent (pinned below so the accent picker can't tint it).
   Like Noir it grayscales the whole document, so it's a true black-&-white light
   theme. --primary is black and the app hardcodes white-on-primary, so filled
   primary controls (white text on black) stay legible with no override — the
   mirror image of Noir's dark-text-on-white-accent fix. (feat_blanc_theme) */
[data-theme="blanc"] {
  color-scheme: light;
  --bg: #ffffff;
  --surface: #f7f7f7;
  --surface-2: #ededed;
  --border: #e0e0e0;
  --primary: #000000;
  --primary-dark: #1f1f1f;
  /* Secondary/tertiary kept as light greys so the monochrome look holds AND
     white-on-accent (.btn-secondary etc.) stays legible without overrides. */
  --secondary: #bdbdbd;
  --secondary-dark: #ababab;
  --tertiary: #ababab;
  --tertiary-dark: #bdbdbd;
  --text: #0a0a0a;
  --text-secondary: #6b6b6b;
  --danger: #ef4444;
  --success: #22c55e;
  --warning: #f59e0b;
  --scrim: rgba(255, 255, 255, 0.92);
  --ring-track: rgba(0, 0, 0, 0.1);
  --phase-red: #ef4444;
  --phase-orange: #f97316;
  --phase-yellow: #eab308;
  --phase-green: #22c55e;
}
/* On near-white, surfaces sit a hair off the background — a hairline border gives
   cards/inputs a definite edge (mirrors Noir's on-black treatment). */
[data-theme="blanc"] .card,
[data-theme="blanc"] .section-card,
[data-theme="blanc"] .lift-card,
[data-theme="blanc"] .workout-card { border: 1px solid var(--border); }
/* A light .btn-surface on white loses its edge — give every button a slim border
   so they stay discernible (the light-theme counterpart of fix_noir_theme_button_border). */
[data-theme="blanc"] .btn { border: 1px solid var(--border); }
/* Fully black & white: desaturate everything the palette can't reach — coloured
   emoji, SVG icons, photos, charts, semantic colours. Grayscale on the document
   ROOT (exempt from creating a containing block, so fixed overlays aren't broken)
   does it in one stroke, exactly like Noir. (feat_blanc_theme) */
html[data-theme="blanc"] { filter: grayscale(1); }

/* Chroma — a saturated, jewel-toned dark theme: a deep indigo/violet base with an
   electric violet primary and cyan + magenta secondaries. Unlike Dark (which is a
   neutral slate that defers to the accent picker), Chroma is OPINIONATED like
   Noir/Blanc — it pins its own vivid accent below so the whole app stays colourful
   whatever accent is stored. --primary is a purple-600 so the app's hardcoded
   white-on-primary stays legible. (feat_pwa_chroma_theme) */
[data-theme="chroma"] {
  color-scheme: dark;
  --bg: #140a2e;
  --surface: #211648;
  --surface-2: #322169;
  --border: #4a3491;
  --primary: #9333ea;
  --primary-dark: #7e22ce;
  --secondary: #22d3ee;
  --secondary-dark: #06b6d4;
  --tertiary: #f472b6;
  --tertiary-dark: #ec4899;
  --text: #f5f3ff;
  --text-secondary: #b7a9e0;
  --danger: #fb7185;
  --success: #34d399;
  --warning: #fbbf24;
  /* Violet-tinted scrim + ring track and a vivid phase ramp, so the timer/stopwatch
     carry the theme's colour. */
  --scrim: rgba(10, 4, 26, 0.86);
  --ring-track: rgba(168, 85, 247, 0.16);
  --phase-red: #fb7185;
  --phase-orange: #fb923c;
  --phase-yellow: #facc15;
  --phase-green: #34d399;
}

/* Sepia — a warm, parchment-toned LIGHT theme: aged-paper surfaces, espresso-brown
   text, and an opinionated walnut-brown accent pinned below (like Noir/Blanc/Chroma)
   so the whole app stays warm whatever accent is stored. Deliberately NOT grayscaled
   — the warm tone IS the theme. --primary is a mid walnut, dark enough that the app's
   hardcoded white-on-primary stays legible. (feat_theme_sepia) */
[data-theme="sepia"] {
  color-scheme: light;
  --bg: #ece0c8;
  --surface: #f7eddb;
  --surface-2: #e2d2b0;
  --border: #cbb994;
  --primary: #7c5a3a;
  --primary-dark: #634527;
  --secondary: #9c6b3f;
  --secondary-dark: #7c5228;
  --tertiary: #b08d57;
  --tertiary-dark: #8f6f3e;
  --text: #3b2f1e;
  --text-secondary: #6f5b40;
  --danger: #a3341e;
  --success: #4d7c2f;
  --warning: #b5791a;
  /* Warm parchment scrim + ring track and a phase ramp that stays legible on the
     light scrim, so the timer/stopwatch carry the theme's warm tone. */
  --scrim: rgba(237, 224, 200, 0.92);
  --ring-track: rgba(59, 47, 30, 0.14);
  --phase-red: #b91c1c;
  --phase-orange: #c2410c;
  --phase-yellow: #a16207;
  --phase-green: #4d7c2f;
}
/* On the low-contrast parchment, cards sit only a hair off the background — a hairline
   border gives them a definite edge (mirrors Blanc's on-white treatment). */
[data-theme="sepia"] .card,
[data-theme="sepia"] .section-card,
[data-theme="sepia"] .lift-card,
[data-theme="sepia"] .workout-card { border: 1px solid var(--border); }

/* "Flatten icons" (feat_theme_flatten_icons_and_accent_lock) — recolour every icon
   glyph into a solid single-colour silhouette in the ACCENT. Line/mask icons already
   follow --primary; this targets the colourful EMOJI, which flatten.js wraps in
   .app-icon so the recolour hits the glyph and not the surrounding text.
   color:transparent hides the emoji's own colours; the zero-offset text-shadow
   repaints the glyph outline in the accent. Active only while data-flatten="1"
   (Noir/Blanc force it on and already grayscale on top; Light/Dark/High-contrast
   toggle it via the Settings checkbox). */
/* display:contents so the wrapper generates NO box: the emoji text rejoins the
   surrounding text as one run and doesn't become a separate flex/grid item (which
   spread section <summary> rows edge-to-edge — the "justified" regression). The
   recolour still applies because color + text-shadow are inherited by the emoji
   text from this element. (fix_flatten_app_icon_summary_layout) */
.app-icon { display: contents; }
/* Borderless edit affordance — a bare pencil, no button chrome (owner 2026-07-07). Used for the
   Account username / password edit pencils. */
.settings-edit-btn { border: none; background: transparent; color: var(--text-secondary); cursor: pointer; display: inline-flex; align-items: center; padding: 4px; border-radius: 4px; transition: color 0.15s; }
.settings-edit-btn:hover { color: var(--primary); }
html[data-flatten="1"] .app-icon {
  color: transparent;
  text-shadow: 0 0 0 var(--primary);
}
/* "Invert" (feat_flatten_icons_invert_secondary_accent): while flattened, recolour the
   icon silhouettes into the accent's paired SECONDARY colour (--accent-danger — the
   scheme's live second colour shown next to it in the picker, amber for charcoal) instead
   of --primary. Covers the emoji silhouettes plus the accent-following line/mask/nav icons
   so the whole icon set stays one colour. Only active with data-flatten-invert="1" (default
   on for the charcoal accent). Higher specificity (html + 2 attrs + class) beats the per-nav
   `.tab .icon-mask { color: --primary }` rules; it targets icons only, so text labels keep
   the accent. NB: this tracks --accent-danger (the accent's paired swatch), NOT the theme's
   --danger — for charcoal the two differ (amber #f59e0b vs the theme red), which is exactly
   the case the checkbox is designed for. (fix_flatten_invert_accent_paired_colour) */
html[data-flatten="1"][data-flatten-invert="1"] .app-icon {
  text-shadow: 0 0 0 var(--accent-danger);
}
html[data-flatten="1"][data-flatten-invert="1"] .icon-mask,
html[data-flatten="1"][data-flatten-invert="1"] .icon-glyph,
html[data-flatten="1"][data-flatten-invert="1"] .tab svg,
html[data-flatten="1"][data-flatten-invert="1"] .tab-tool svg {
  color: var(--accent-danger);
}

/* Accent colours (user-pickable in Settings, see js/accent.js). A separate axis
   from the theme: each overrides --primary / --primary-dark. Scoped to exclude
   high-contrast, which keeps its own AAA-tuned blue. Default 'blue' matches the
   app's original accent, so an un-set user sees no change. (user 2026-06-09) */
html:not([data-theme="high-contrast"])[data-accent="blue"]    { --primary: #3b82f6; --primary-dark: #2563eb; }
html:not([data-theme="high-contrast"])[data-accent="indigo"]  { --primary: #6366f1; --primary-dark: #4f46e5; }
html:not([data-theme="high-contrast"])[data-accent="violet"]  { --primary: #8b5cf6; --primary-dark: #7c3aed; }
html:not([data-theme="high-contrast"])[data-accent="emerald"] { --primary: #10b981; --primary-dark: #059669; }
html:not([data-theme="high-contrast"])[data-accent="teal"]    { --primary: #06b6d4; --primary-dark: #0891b2; }
html:not([data-theme="high-contrast"])[data-accent="amber"]   { --primary: #f59e0b; --primary-dark: #d97706; }
html:not([data-theme="high-contrast"])[data-accent="rose"]    { --primary: #f43f5e; --primary-dark: #e11d48; }
html:not([data-theme="high-contrast"])[data-accent="pink"]    { --primary: #ec4899; --primary-dark: #db2777; }
/* Charcoal scheme (feat_accent_two_colour_schemes): a monochrome slate accent — dark
   enough for white text on light themes, light enough to stand off the dark themes'
   near-black surfaces (noir's button border covers pure black). Paired with an amber
   danger below. */
html:not([data-theme="high-contrast"])[data-accent="charcoal"] { --primary: #374151; --primary-dark: #1f2937; --accent-danger: #f59e0b; }

/* Accent-coloured TEXT links (help/hint copy) track the accent by default. But the
   CHARCOAL accent is a dark slate tuned for button BACKGROUNDS (white text on it);
   as TEXT on the dark theme's dark surface it's unreadable — so lift links to a
   readable light slate there. (owner 2026-07-04: assistance-hint link unreadable in
   dark mode.) */
:root { --link: var(--primary); }
/* The accent's paired SECONDARY colour (the swatch shown beside it in the picker), used
   by the flatten "Invert" recolour. Defaults to the theme's --danger — so cool accents
   (whose picker danger IS red) and the warm accents keep today's behaviour — and only
   charcoal overrides it to its amber pairing (#f59e0b, set on the charcoal accent rule
   above). Custom schemes set --danger inline (accent.js), so this fallback follows them
   automatically. (fix_flatten_invert_accent_paired_colour) */
:root { --accent-danger: var(--danger); }
html[data-theme="dark"][data-accent="charcoal"],
html:not([data-theme])[data-accent="charcoal"] { --link: #94a3b8; }

/* Charcoal is our DEFAULT scheme: a dark slate tuned for white-text buttons on the
   LIGHT themes. On the DARK themes that near-black fill vanishes into the dark
   surfaces — buttons AND icons don't stand out at all. So on dark + charcoal we
   REVERSE the accent to a LIGHT slate (with dark text on it), the way amber/noir
   pop against black. Scoped to charcoal only — the bright accents already stand off
   the dark surfaces and want their own hue. (owner 2026-07-04: dark+charcoal buttons
   don't stand out; reverse it like amber.) */
html[data-theme="dark"][data-accent="charcoal"],
html:not([data-theme])[data-accent="charcoal"] {
  --primary: #cbd5e1;       /* slate-300 — light, stands off the dark surfaces */
  --primary-dark: #e2e8f0;  /* slate-200 — hover lightens, like a light button */
}
/* The now-light accent means anything FILLED with --primary needs DARK text (the app
   hardcodes white-on-primary). Same control list as Noir's white-accent fix. */
html[data-theme="dark"][data-accent="charcoal"] .btn-primary,
html[data-theme="dark"][data-accent="charcoal"] .btn-secondary,
html[data-theme="dark"][data-accent="charcoal"] .accent-custom-wrap.is-active,
html[data-theme="dark"][data-accent="charcoal"] .log-pill,
html[data-theme="dark"][data-accent="charcoal"] .settings-tab.active,
html[data-theme="dark"][data-accent="charcoal"] .hub-seg-opt.active,
html[data-theme="dark"][data-accent="charcoal"] .data-migration-banner,
html[data-theme="dark"][data-accent="charcoal"] .section-card[open] > summary::after,
html[data-theme="dark"][data-accent="charcoal"] .accent-gradient,
html[data-theme="dark"][data-accent="charcoal"] .grace-cta,
html:not([data-theme])[data-accent="charcoal"] .btn-primary,
html:not([data-theme])[data-accent="charcoal"] .btn-secondary,
html:not([data-theme])[data-accent="charcoal"] .accent-custom-wrap.is-active,
html:not([data-theme])[data-accent="charcoal"] .log-pill,
html:not([data-theme])[data-accent="charcoal"] .settings-tab.active,
html:not([data-theme])[data-accent="charcoal"] .hub-seg-opt.active,
html:not([data-theme])[data-accent="charcoal"] .data-migration-banner,
html:not([data-theme])[data-accent="charcoal"] .section-card[open] > summary::after,
html:not([data-theme])[data-accent="charcoal"] .accent-gradient,
html:not([data-theme])[data-accent="charcoal"] .grace-cta { color: #111827; }

/* Secondary + tertiary accents — same hue swatches as the primary accent, picked
   independently per channel (data-secondary / data-tertiary, js/accent.js). Same
   high-contrast exemption. A 'custom' choice sets the var inline (no rule here),
   so the inline style wins. (feat_secondary_accent_colours) */
html:not([data-theme="high-contrast"])[data-secondary="blue"]    { --secondary: #3b82f6; --secondary-dark: #2563eb; }
html:not([data-theme="high-contrast"])[data-secondary="indigo"]  { --secondary: #6366f1; --secondary-dark: #4f46e5; }
html:not([data-theme="high-contrast"])[data-secondary="violet"]  { --secondary: #8b5cf6; --secondary-dark: #7c3aed; }
html:not([data-theme="high-contrast"])[data-secondary="emerald"] { --secondary: #10b981; --secondary-dark: #059669; }
html:not([data-theme="high-contrast"])[data-secondary="teal"]    { --secondary: #06b6d4; --secondary-dark: #0891b2; }
html:not([data-theme="high-contrast"])[data-secondary="amber"]   { --secondary: #f59e0b; --secondary-dark: #d97706; }
html:not([data-theme="high-contrast"])[data-secondary="rose"]    { --secondary: #f43f5e; --secondary-dark: #e11d48; }
html:not([data-theme="high-contrast"])[data-secondary="pink"]    { --secondary: #ec4899; --secondary-dark: #db2777; }
html:not([data-theme="high-contrast"])[data-tertiary="blue"]     { --tertiary: #3b82f6; --tertiary-dark: #2563eb; }
html:not([data-theme="high-contrast"])[data-tertiary="indigo"]   { --tertiary: #6366f1; --tertiary-dark: #4f46e5; }
html:not([data-theme="high-contrast"])[data-tertiary="violet"]   { --tertiary: #8b5cf6; --tertiary-dark: #7c3aed; }
html:not([data-theme="high-contrast"])[data-tertiary="emerald"]  { --tertiary: #10b981; --tertiary-dark: #059669; }
html:not([data-theme="high-contrast"])[data-tertiary="teal"]     { --tertiary: #06b6d4; --tertiary-dark: #0891b2; }
html:not([data-theme="high-contrast"])[data-tertiary="amber"]    { --tertiary: #f59e0b; --tertiary-dark: #d97706; }
html:not([data-theme="high-contrast"])[data-tertiary="rose"]     { --tertiary: #f43f5e; --tertiary-dark: #e11d48; }
html:not([data-theme="high-contrast"])[data-tertiary="pink"]     { --tertiary: #ec4899; --tertiary-dark: #db2777; }

/* Noir pins a monochrome white accent regardless of the accent picker (mirrors how
   high-contrast pins its blue). Placed AFTER the accent rules so it wins the
   equal-specificity tie when data-accent/-secondary/-tertiary are set. (feat_theme_noir) */
html[data-theme="noir"][data-accent]    { --primary: #ffffff; --primary-dark: #e0e0e0; }
html[data-theme="noir"][data-secondary] { --secondary: #404040; --secondary-dark: #525252; }
html[data-theme="noir"][data-tertiary]  { --tertiary: #525252; --tertiary-dark: #404040; }
/* Blanc pins a monochrome BLACK accent regardless of the accent picker — the
   inverse of Noir's pin above. (feat_blanc_theme) */
html[data-theme="blanc"][data-accent]    { --primary: #000000; --primary-dark: #1f1f1f; }
html[data-theme="blanc"][data-secondary] { --secondary: #bdbdbd; --secondary-dark: #ababab; }
html[data-theme="blanc"][data-tertiary]  { --tertiary: #ababab; --tertiary-dark: #bdbdbd; }
/* Chroma pins its vivid violet/cyan/magenta accent regardless of the accent picker
   (same equal-specificity-then-source-order trick as Noir/Blanc), so its colour
   identity holds however the user's accent is set. (feat_pwa_chroma_theme) */
html[data-theme="chroma"][data-accent]    { --primary: #9333ea; --primary-dark: #7e22ce; --accent-danger: #22d3ee; }
html[data-theme="chroma"][data-secondary] { --secondary: #22d3ee; --secondary-dark: #06b6d4; }
html[data-theme="chroma"][data-tertiary]  { --tertiary: #f472b6; --tertiary-dark: #ec4899; }
/* Sepia pins its warm walnut accent regardless of the picker — opinionated like
   Chroma. --accent-danger pinned to a warm brick so the flatten-invert recolour
   stays in the sepia family. (feat_theme_sepia) */
/* --accent-danger drives the flattened icon silhouettes (flatten-invert, default on for
   charcoal) + the Primary pill. In Sepia the old brick red (#a3341e) read too vibrant/red
   against the parchment; a flatter, browner tone keeps the sepia warmth. (fix_sepia_icon_flatter_brown) */
html[data-theme="sepia"][data-accent]    { --primary: #7c5a3a; --primary-dark: #634527; --accent-danger: #8a5a3a; }
html[data-theme="sepia"][data-secondary] { --secondary: #9c6b3f; --secondary-dark: #7c5228; }
html[data-theme="sepia"][data-tertiary]  { --tertiary: #b08d57; --tertiary-dark: #8f6f3e; }
/* The flatten "Invert" recolour paints icons in --accent-danger (the accent's paired
   swatch). The default charcoal accent pairs to amber (#f59e0b), which clashes badly
   with Chroma's violet/indigo palette (orange icons). Chroma pins its own paired
   colour — its vivid cyan secondary — so flattened icons read as an on-theme
   violet+cyan pair whatever accent is stored. Two-attribute selector + placed after
   the charcoal rule so it wins. (fix_chroma_icon_colour_off_theme) */

/* On the red-family accents — rose, pink, amber, violet — the standard danger
   red blends into the accent and stops reading as the destructive action, so the
   danger button switches to a high-contrast neutral: WHITE on the dark theme
   (default) and BLACK on the light theme, so it stays legible + distinct on each
   surface. Every other accent + high-contrast keep red. (user 2026-06-12) */
html:not([data-theme="high-contrast"])[data-accent="rose"]   .btn-danger,
html:not([data-theme="high-contrast"])[data-accent="pink"]   .btn-danger,
html:not([data-theme="high-contrast"])[data-accent="amber"]  .btn-danger,
html:not([data-theme="high-contrast"])[data-accent="violet"] .btn-danger {
  background: #f1f5f9;   /* dark theme (default): white button, dark text */
  color: #0f172a;
}
html[data-theme="light"][data-accent="rose"]   .btn-danger,
html[data-theme="light"][data-accent="pink"]   .btn-danger,
html[data-theme="light"][data-accent="amber"]  .btn-danger,
html[data-theme="light"][data-accent="violet"] .btn-danger {
  background: #111827;   /* light theme: black button, white text */
  color: #fff;
}
/* Charcoal scheme → amber danger (amber is visible on every theme; dark text stays
   legible, and it pops against the monochrome accent). (feat_accent_two_colour_schemes) */
html:not([data-theme="high-contrast"])[data-accent="charcoal"] .btn-danger {
  background: #f59e0b;
  color: #111827;
}

/* Accent picker swatches in Settings. */
/* White-label icon override (feat_tenant_branding_icons): a tenant-uploaded glyph
   rendered in place of a built-in. Sized to the surrounding text by default. */
.icon-glyph { display: inline-block; width: 1em; height: 1em; vertical-align: -0.125em; object-fit: contain; }
.accent-btn { display: inline-flex; align-items: center; gap: 7px; }
.accent-dot { width: 14px; height: 14px; border-radius: 50%; display: inline-block; flex: none; box-shadow: inset 0 0 0 1px rgba(0,0,0,0.15); }
/* The paired danger swatch in a two-colour scheme button — a smaller dot tucked
   against the accent dot. (feat_accent_two_colour_schemes) */
.accent-dot--danger { width: 10px; height: 10px; margin-left: -5px; }
/* Marks the default swatch in a channel so the baseline is identifiable. */
.accent-default-tag { display: inline-flex; align-items: center; font-size: 9px; text-transform: uppercase; letter-spacing: .04em; opacity: .75; margin-left: 2px; border: 1px solid currentColor; border-radius: 4px; padding: 1px 3px 0; line-height: 1; }

/* Clear-data / delete-account "type your username" copy affordance: the name and
   the copy icon share ONE bordered box (bordered on all sides), and the icon
   flips to a tick for 5s on copy. (fix_delete_user_copy_button_border_tick) */
.cd-copy-field { display: inline-flex; align-items: center; gap: 6px; max-width: 100%; }
.cd-copy-field #cd-username { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cd-copy-btn { flex: none; border: none; background: transparent; color: var(--text-secondary); cursor: pointer; display: inline-flex; align-items: center; padding: 2px; border-radius: 4px; transition: color 0.15s; }
.cd-copy-btn:hover { color: var(--text); }
.cd-copy-btn.copied { color: var(--success, var(--primary)); }
/* Per-channel accent picker rows (primary/secondary/tertiary) + the custom-colour
   swatch that opens the native all-colour selector. (feat_secondary_accent_colours) */
.accent-row { margin-top: 10px; }
.accent-row:first-of-type { margin-top: 0; }
.accent-row-label { font-weight: 600; margin: 0 0 6px 4px; }
.accent-custom-wrap {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 14px; font-size: 13px; font-weight: 600;
  border-radius: var(--radius-sm); background: var(--surface-2); color: var(--text);
  cursor: pointer;
}
.accent-custom-wrap.is-active { background: var(--primary); color: #fff; }
.accent-custom {
  width: 14px; height: 14px; flex: none; padding: 0; border: none;
  border-radius: 50%; background: none; cursor: pointer;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,0.15);
}
.accent-custom::-webkit-color-swatch-wrapper { padding: 0; }
.accent-custom::-webkit-color-swatch { border: none; border-radius: 50%; }
.accent-custom::-moz-color-swatch { border: none; border-radius: 50%; }

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

/* Anchors inherit the surrounding text colour by default. Much of the app uses
   <a> as cards/rows/buttons (e.g. .active-program-card, .btn, .badge), and with no
   base colour those fell to the UA link palette — most visibly the purple
   *visited*-link colour bleeding into headings like the home hub's "Up Next: <lift>"
   (user 2026-07-03). Genuine text-links opt back in explicitly (.help-content a,
   .switch-link a, .analytics-consent-text a → var(--primary)). */
a { color: inherit; }

/* Checkboxes / radios follow the theme accent instead of the browser default
   (which ignored dark mode + the accent picker). (user 2026-06-09) */
input[type="checkbox"], input[type="radio"] { accent-color: var(--primary); }

/* DEFAULT styling for text-like controls so a bare <input>/<select>/<textarea>
   is never an unstyled native control. Low specificity on purpose — explicit
   classes (.form-group input, .input-inline, .settings-select, …) and inline
   styles still win, so this only catches controls nobody styled. Width is left
   to context (flex rows etc.). (user 2026-06-15: "every single time you add a
   control you don't style it") */
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]),
select,
textarea {
  padding: 10px 12px;
  background: var(--surface);
  border: 1px solid var(--surface-2);
  border-radius: var(--radius-sm);
  color: var(--text);
  font-size: 15px;
  font-family: inherit;
  outline: none;
  transition: border-color 0.2s;
}
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):focus,
select:focus,
textarea:focus { border-color: var(--primary); }
/* Placeholders are paler than a real value, so a placeholder (e.g. a suggested value) never reads
   as an already-filled field (owner 2026-07-07). --text-secondary at 0.6 sits clearly below the
   --text of a typed value. More-specific placeholder rules (rest-timer / plate cell) still win. */
::placeholder { color: var(--text-secondary); opacity: 0.6; }

/* Custom dropdown chevron, inset from the right edge (the native arrow sits flush to the
   box edge — owner 2026-06-30). `select:not([multiple])` at specificity (0,1,1) beats the
   single-class select rules, so every dropdown gets the inset arrow without touching each
   class. Neutral grey reads on every theme. */
select:not([multiple]) {
  -webkit-appearance: none; -moz-appearance: none; appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none' stroke='%23888' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 12px;
  padding-right: 34px;
}

html, body {
  min-height: 100%;
  /* Always reserve the vertical scrollbar gutter so the layout-viewport width is
     identical whether or not a page overflows. Without this, a route that scrolls
     (e.g. Settings) is ~15px narrower than one that doesn't (e.g. Forge), and the
     fixed .tab-bar + centred #app — both anchored via left:50%/margin:auto — jump
     horizontally between the two. (fix_toolbar_static_forge_settings) */
  scrollbar-gutter: stable;
  /* --font is the white-label override (feat_tenant_branding_fonts); unset = the
     system stack. Tenant fonts: bundled @font-face below, a Google <link>, or an
     uploaded @font-face injected at runtime by tenant-theme.js. */
  font-family: var(--font, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
  background: var(--bg);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
}

#app {
  padding: 16px;
  /* Clear the device status bar / notch in the Android (Capacitor) shell.
     env(safe-area-inset-top) is 0 in a normal browser, so this is a no-op there. */
  padding-top: calc(16px + env(safe-area-inset-top));
  /* 80px clears the fixed tab bar; add the device bottom inset (home indicator /
     gesture bar) so the last element isn't clipped on devices where the tab bar
     grows by env(safe-area-inset-bottom). 0 in a normal browser. */
  padding-bottom: calc(80px + env(safe-area-inset-bottom));
  max-width: 480px;
  margin: 0 auto;
  width: 100%;
}

/* Tab bar — matches the 480px content column; on phones (viewport ≤ 480px)
   width:100% takes effect first so the bar still goes edge-to-edge. */
.tab-bar {
  position: fixed;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 100%;
  max-width: 480px;
  display: flex;
  background: var(--surface);
  border-top: 1px solid var(--surface-2);
  padding: 8px 0;
  padding-bottom: max(8px, env(safe-area-inset-bottom));
  z-index: 100;
}

.tab-bar.hidden { display: none; }

/* App-shell notification bell — fixed top-right, above page content. */
.notif-bell {
  position: fixed;
  top: max(10px, env(safe-area-inset-top));
  right: 12px;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  background: var(--surface);
  color: var(--text-secondary);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
  cursor: pointer;
  z-index: 200;
}
.notif-bell.hidden { display: none; }
/* Unsaved-data alert — amber chip just left of the notification bell, shown
   whenever there are writes captured offline and not yet synced. (user 2026-06-11) */
.unsaved-alert {
  position: fixed;
  top: max(10px, env(safe-area-inset-top));
  right: 60px;
  height: 40px;
  padding: 0 12px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  border: none;
  border-radius: 20px;
  background: var(--warning, #b45309);
  color: #fff;
  font-weight: 800;
  font-size: 14px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
  cursor: pointer;
  z-index: 200;
}
.unsaved-alert.hidden { display: none; }
.unsaved-alert__count { font-variant-numeric: tabular-nums; }
.notif-bell__badge {
  position: absolute;
  top: -2px;
  right: -2px;
  min-width: 18px;
  height: 18px;
  padding: 0 4px;
  border-radius: 9px;
  background: #ef4444;
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
}
.notif-bell__badge.hidden { display: none; }

/* Unread reminder in the notifications feed — an accent rail until read on open. */
.notif-unread { border-left: 3px solid var(--accent, var(--primary, #c0392b)); }

.tab {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  text-decoration: none;
  color: var(--text-secondary);
  font-size: 11px;
  font-weight: 500;
  transition: color 0.2s;
}

/* One standard: every nav/toolbar icon follows the theme accent (--primary),
   active/inactive differentiated by opacity. The anvil/cog brand marks are
   rendered as CSS masks (.icon-mask) so a single background-color re-tints them
   to the accent; the inline Progress flame + middle-tab option glyphs use
   currentColor. (Tenant-override images stay as <img> and keep their own art.) */
.icon-mask {
  display: inline-block;
  width: 24px;
  height: 24px;
  background-color: currentColor;
  -webkit-mask: var(--icon-src) center / contain no-repeat;
  mask: var(--icon-src) center / contain no-repeat;
}
.tab svg { width: 24px; height: 24px; color: var(--primary); opacity: 0.55; transition: opacity 0.2s; }
.tab .icon-mask { color: var(--primary); opacity: 0.55; transition: opacity 0.2s; }
.tab img { width: 24px; height: 24px; object-fit: contain; opacity: 0.55; transition: opacity 0.2s; }
.tab.active svg,
.tab.active .icon-mask,
.tab.active img { opacity: 1; }
.tab.active { color: var(--primary); }

/* Training toolbar (feat_training_toolbar_tools): while a workout is open the
   bottom nav becomes the three workout tools. The tool buttons reuse .tab for
   layout; reset the native <button> chrome and tint the (currentColor) glyphs to
   the accent so they read as active controls, not inactive nav. */
.tab-tool {
  background: none;
  border: none;
  margin: 0;
  padding: 0;
  font: inherit;
  cursor: pointer;
  color: var(--primary);
  -webkit-tap-highlight-color: transparent;
}
.tab-tool svg { opacity: 0.9; }
/* The Forge (home) tool uses the anvil mask (.icon-mask), not an inline <svg> —
   .tab-tool already sets color:var(--primary), so it renders as a bright accent
   silhouette matching its Timer/Stopwatch siblings. (.tab-tool img is kept for
   tenant-override art, which stays at its own colour.) */
.tab-tool .icon-mask { opacity: 0.9; }
.tab-tool img { opacity: 0.9; }
/* Blanked tool (e.g. Timer while a rest runs / Stopwatch while cardio is open):
   keep the flex slot so the row doesn't reflow, but hide + disable it — the
   minimised countdown / cardio chip owns its corner instead. */
.tab-tool--blank { visibility: hidden; pointer-events: none; }
/* Timer tool while a closed rest is still counting: a gentle pulse hints that a
   tap RESUMES the countdown rather than starting a fresh rest. */
.tab-tool--resuming svg { opacity: 1; animation: tab-tool-pulse 1.2s ease-in-out infinite; }
@keyframes tab-tool-pulse { 50% { opacity: 0.4; } }

/* Mini rest timer (feat_toolbar_mini_timer_button): the centre Timer button is a
   live minimised countdown while a rest runs — a draining ring with the mm:ss (or
   idle GO!) centred INSIDE it. The ring grows to ~the full height of an icon+label
   tab so it fills the slot without making the bar taller / reflowing its siblings.
   Colour ramps like the full timer. */
.tab-tool--mini { justify-content: center; overflow: visible; }
.tab-mini-ring {
  /* 58px so the timer dominates — but a negative top margin keeps its LAYOUT height
     equal to the icon+label tabs (~43px) so the bottom bar does NOT grow taller; the
     ring pokes above the bar as a raised focal button. The disc carries the SAME
     surface fill + 1px border + top shadow as the toolbar, so the part sticking out
     reads as the bar bulging up (owner 2026-07-04). The uniform 15px countdown (even
     10:00 / 60:00) fits inside — measured against a stroke-8 ring. */
  position: relative;
  width: 58px;
  height: 58px;
  margin-top: -15px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface);
  border: 1px solid var(--surface-2);
  border-radius: 50%;
  box-shadow: 0 -2px 6px rgba(0, 0, 0, 0.07);
}
.tab-mini-ring svg { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
/* stroke-width 6 (was 8): a slightly thinner mini ring. (fix_mini_timer_border_reset_and_thinner) */
.tab-mini-track { fill: none; stroke: var(--surface-2); stroke-width: 6; }
.tab-mini-prog {
  fill: none;
  stroke-width: 6;
  stroke-linecap: round;
  /* Drain smoothly between the once-a-second updates. */
  transition: stroke-dashoffset 0.9s linear;
}
/* Ramp mirrors the full timer's colours. */
.tab-mini-ring.red .tab-mini-prog { stroke: #ef4444; }
.tab-mini-ring.orange .tab-mini-prog { stroke: #f97316; }
.tab-mini-ring.yellow .tab-mini-prog { stroke: #eab308; }
.tab-mini-ring.green .tab-mini-prog { stroke: #22c55e; }
.tab-mini-time {
  position: relative;
  font-size: 15px;            /* uniform for every countdown value, incl. 10:00 */
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--primary);
}
.tab-mini-time.is-go { font-size: 18px; letter-spacing: 0; }

/* Typography */
h1 { font-size: 24px; font-weight: 700; margin-bottom: 16px; }
h2 { font-size: 18px; font-weight: 600; margin-bottom: 12px; }
h3 { font-size: 15px; font-weight: 600; margin-bottom: 8px; color: var(--text-secondary); }

/* Cards */
.card {
  background: var(--surface);
  border-radius: var(--radius);
  padding: 16px;
  margin-bottom: 12px;
}

/* Collapsible accordion section — used by the Settings Account tab so the
   long list of preferences collapses to a tidy stack, expanding one row at a
   time. */
.section-card {
  padding: 0;
}
.section-card > summary {
  padding: 16px;
  cursor: pointer;
  list-style: none;
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  position: relative;
}
.section-card > summary::-webkit-details-marker { display: none; }
/* Prominent expand/collapse affordance: a filled circular badge (::after) with a
   border-drawn chevron (::before) centred on it. The chevron is drawn from two
   borders rather than a text glyph because a glyph's optical centre ≠ its box
   centre, so rotating it drifted (collapsed read "too low", open "too far left").
   A border chevron rotates cleanly around its true centre in both states.
   (feat_settings_equipment_polish; was user 2026-06-11) */
.section-card > summary::after {
  content: '';
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--surface-2);
  transition: background 0.18s;
}
.section-card[open] > summary::after { background: var(--primary); }
/* The chevron (::before) is a box that EXACTLY overlaps the badge (same right
   edge + width), with the arrow drawn by a mask centred in that box — so it is
   always dead-centre on the badge regardless of layout, and rotating it can't
   drift it out of the circle. Colour comes from `background` (themeable). */
.section-card > summary::before {
  content: '';
  position: absolute;
  top: 50%;
  right: 15px;       /* chevron nudged 1px right of the badge centre (chore_settings_header_polish) */
  width: 30px;       /* identical box to the badge */
  height: 30px;
  background: var(--primary);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9 6l6 6-6 6' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 11px 11px no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9 6l6 6-6 6' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 11px 11px no-repeat;
  transform: translateY(-50%) rotate(0deg); /* point right (collapsed) */
  transition: transform 0.18s ease, background 0.18s;
  pointer-events: none;
}
.section-card[open] > summary::before {
  transform: translateY(-50%) rotate(90deg); /* point down (open) */
  background: #fff;
}
.section-card .section-body { padding: 0 16px 16px; }
.section-card .section-summary-value {
  color: var(--text-secondary);
  font-weight: 500;
  font-size: 13px;
  margin-left: auto;
  margin-right: 8px;
  text-align: right; /* subtitle sits flush right beside the expand button (owner 2026-07-08) */
}
/* Give each section summary's leading icon a fixed-width, centred slot so every
   TITLE starts at the same x — the icons are emoji with differing advance widths,
   so without a slot the titles drift down the page. The global `.app-icon` is
   `display:contents` (so flatten doesn't turn the emoji into a stray flex item that
   space-between would fling to the row's edge); scoped to summaries we opt back into
   a box. To keep every row's value + chevron pinned right (and never spread the
   icon/title apart), the chevron badge also takes `margin-left:auto` — so rows
   WITHOUT a `.section-summary-value` (which carries the auto-margin) still push the
   badge to the edge instead of justifying. (fix_chroma_settings_polish) */
.section-card > summary .app-icon {
  display: inline-block;
  width: 1.4em;
  flex-shrink: 0;
  text-align: center;
  font-style: normal;
  /* Cancel most of the summary's 12px flex gap on the icon→title side so the slot
     doesn't widen the row (which wrapped the longest title, "Accent Colours"); the
     gap still spaces title↔badge↔value normally. */
  margin-right: -6px;
}
/* Only rows WITHOUT a `.section-summary-value` need the badge to absorb the free
   space (the value carries `margin-left:auto` when present). A SECOND auto margin
   on the badge would split the free space with the value's auto and strand it
   mid-row instead of flush against the badge. (fix_settings_subtitle_flush_right) */
.section-card > summary:not(:has(.section-summary-value))::after { margin-left: auto; }

.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px 20px;
  border: none;
  border-radius: var(--radius-sm);
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.2s, opacity 0.2s;
  width: 100%;
  color: var(--text);
  /* .btn is also used on <a> elements (e.g. the workout-page "← Back" link).
     Reset the browser default anchor underline so anchor-as-button controls
     match real <button>s. */
  text-decoration: none;
}

.btn:disabled { opacity: 0.5; cursor: not-allowed; }
/* Solid colour-filled buttons keep light text on both themes (the body text
   colour flips to dark in light mode, which would be illegible on these). */
.btn-primary { background: var(--primary); color: #fff; }
.btn-primary:hover:not(:disabled) { background: var(--primary-dark); }
/* --danger-fg lets a CUSTOM scheme's danger carry a legible text colour (a light
   custom danger auto-switches to dark text). Named schemes set text in the accent
   rules above; the default is white. (feat_accent_two_colour_schemes) */
.btn-danger { background: var(--danger); color: var(--danger-fg, #fff); }
.btn-success { background: var(--success); color: #fff; }
.btn-surface { background: var(--surface-2); }
/* Secondary/tertiary accents were dropped (feat_accent_two_colour_schemes) — these
   legacy classes now follow the single --primary accent. */
.btn-secondary { background: var(--primary); color: #fff; }
.btn-secondary:hover:not(:disabled) { background: var(--primary-dark); }
/* Reusable primary→secondary accent gradient (decorative surfaces only — not the
   rest-timer ring, whose colour encodes time remaining). */
.accent-gradient { background: linear-gradient(135deg, var(--primary), var(--primary-dark)); color: #fff; }

/* Forge-your-program wizard step indicator: a segmented progress bar. Uses --link
   (the readable accent — a legible slate on the low-contrast charcoal scheme) so the
   done/current segments stand out on every theme. (owner 2026-07-04: step markers
   poorly indicated.) */
.cb-wiz-steps { display: flex; gap: 6px; margin-bottom: 8px; }
.cb-wiz-dot { flex: 1; height: 5px; border-radius: 3px; background: var(--surface-2); }
.cb-wiz-dot.done { background: var(--link); opacity: 0.5; }
.cb-wiz-dot.active { background: var(--link); }

/* Goal/priority toggle-pair selections (SELECTED = btn-primary) now rely on the plain
   filled-accent look — a solid --primary button reads clearly against the unselected
   btn-surface on every theme, because the dark+charcoal scheme now REVERSES to a light
   accent in dark mode (see the reversal block above). No ring/checkmark needed — the
   fill alone marks the selection, matching the other Forge-your-program wizard steps.
   (owner 2026-07-04: drop the border, make the selected button stand out by fill.) */
.btn-sm { padding: 8px 14px; font-size: 13px; width: auto; }

.btn-outline {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text);
}

.btn-outline:hover:not(:disabled) { background: var(--surface); }

/* Pill toggle (used for Inverted option) */
.pill-toggle {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 14px 6px 6px;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  background: transparent;
  color: var(--text-secondary);
  align-self: flex-start;
  transition: background 0.2s, color 0.2s, border-color 0.2s;
}
.pill-toggle .pill-dot {
  display: inline-block;
  width: 16px;
  height: 16px;
  border-radius: 999px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  transition: background 0.2s, border-color 0.2s, box-shadow 0.2s;
  flex-shrink: 0;
}
.pill-toggle.active {
  /* Use the user's accent (not a hardcoded indigo) so selected day pills + all
     toggles follow the accent picker. (user 2026-06-14) */
  background: color-mix(in srgb, var(--primary) 12%, transparent);
  border-color: var(--primary);
  color: var(--text);
}
.pill-toggle.active .pill-dot {
  background: var(--primary);
  border-color: var(--primary);
  box-shadow: inset 0 0 0 3px var(--surface);
}
.pill-toggle:hover:not(:disabled) { border-color: var(--primary); }

/* Forms */
.form-group { margin-bottom: 16px; }

.form-group label {
  display: block;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-secondary);
  margin-bottom: 6px;
}

.form-group input, .form-group select {
  width: 100%;
  padding: 12px;
  background: var(--surface);
  border: 1px solid var(--surface-2);
  border-radius: var(--radius-sm);
  color: var(--text);
  font-size: 15px;
  outline: none;
  transition: border-color 0.2s;
}

.form-group input:focus, .form-group select:focus {
  border-color: var(--primary);
}

.form-group input::placeholder { color: var(--text-secondary); }

/* Standalone select control (outside a .form-group) — matches the app's inputs
   so dropdowns like the biometric "lock after" picker don't render bare/native. */
.settings-select {
  padding: 10px 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  font-size: 15px;
  min-width: 180px;
  max-width: 100%;
  outline: none;
  transition: border-color 0.2s;
}
.settings-select:focus { border-color: var(--primary); }

/* Rest-timer duration buckets (Settings → Rest timer durations). Previously each
   input carried repeated inline surface-2/border styling and the reps→rest fields
   sat on one wrapping flex line that broke alignment on narrow screens. These
   classes lean on the themed base inputs and lay the fields out as two tidy
   label-left / control-right rows. (fix_rest_timer_durations_styling) */
.rt-bucket { padding: 12px; margin-top: 8px; }
.rt-bucket-head { display: flex; gap: 8px; align-items: center; }
.rt-bucket-head .rt-bucket-label { flex: 1; min-width: 0; padding: 8px 10px; }
.rt-bucket-fields { margin-top: 8px; display: flex; flex-direction: column; gap: 8px; }
.rt-field-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.rt-field-row > .rt-field-label { color: var(--text-secondary); font-size: 13px; }
.rt-range { display: flex; align-items: center; gap: 6px; }
.rt-range > span { color: var(--text-secondary); font-size: 13px; }
.rt-range input { text-align: right; padding: 8px 10px; }
.rt-range .rt-bucket-min, .rt-range .rt-bucket-max { width: 68px; }
.rt-range .rt-bucket-rest { width: 80px; }
/* Compact rest-timer bucket table (fix_settings_section_summary_wrap): read-only
   rows (label + reps, rest time, edit/delete) that expand inline to an editor. */
.rt-bucket-table { width: 100%; border-collapse: collapse; }
.rt-brow > td { padding: 8px 2px; border-bottom: 1px solid var(--border); vertical-align: middle; }
.rt-brow-main { width: 100%; }
.rt-brow-label { font-weight: 600; }
.rt-brow-rest { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; padding-right: 8px; }
.rt-brow-actions { text-align: right; white-space: nowrap; }
.rt-brow-actions button { background: none; border: none; cursor: pointer; font-size: 15px; line-height: 1; padding: 4px 6px; color: var(--text); }
.rt-brow-editing .rt-e-label { width: 100%; padding: 8px 10px; }
.rt-e-fields { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 8px; }
.rt-e-fields .rt-e-min, .rt-e-fields .rt-e-max { width: 68px; text-align: right; padding: 8px 10px; }
.rt-e-fields .rt-e-rest { width: 80px; text-align: right; padding: 8px 10px; }
.rt-e-fields .rt-field-label { color: var(--text-secondary); font-size: 13px; }
/* Labelled single-control rows in the same section (iso cap, heavy override). */
.rt-labeled-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 8px; }
.rt-labeled-row label { color: var(--text-secondary); font-size: 13px; }
.rt-labeled-row input { width: 96px; text-align: right; padding: 8px 10px; }

/* Top-left back affordance shared across every routed page. Touch-friendly
   target (min-height 40px, generous padding) since this is the primary back
   action on phones — Android hardware-back works but the explicit arrow is
   what the user expects to tap. Borderless (arrow + label only); the hover/
   active background tint is the sole affordance — the old 1px outline read as
   a boxed "line" around the control. */
.btn-back-arrow {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: transparent;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--text);
  padding: 8px 8px 8px 0;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  min-height: 40px;
}
.btn-back-arrow:hover { background: var(--surface); }
.btn-back-arrow:active { background: var(--surface-2); }
.btn-back-arrow svg { display: block; }

/* Password input with show/hide eye toggle. The wrapper is `position: relative`
   so the toggle button can sit inside the input's right edge; the input grows
   its right padding so typed characters don't slide under the icon. */
.password-wrap { position: relative; }
.password-wrap input { padding-right: 44px; }
.password-toggle {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  width: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  color: var(--text-secondary);
}
.password-toggle:hover { color: var(--text); }
.password-toggle:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; border-radius: var(--radius-sm); }
.password-toggle svg { display: block; }

/* The training-days picker rows put a label and a day-of-week select on
   one flex line. .form-group select otherwise stretches to width:100%,
   which steals horizontal space from labels like "Upper Body A" and
   forces a 3-line wrap. Cap the day-select so the label gets the room. */
.lift-order-row .lift-day-select {
  width: auto;
  min-width: 96px;
  flex: 0 0 auto;
}

/* Inline input (for logging sets) */
.input-inline {
  width: 70px;
  padding: 8px;
  background: var(--surface);
  border: 1px solid var(--surface-2);
  border-radius: var(--radius-sm);
  color: var(--text);
  font-size: 14px;
  text-align: center;
  outline: none;
}

.input-inline:focus { border-color: var(--primary); }

/* Stacked single-set editor (.set-editor): a small field label above each
   input, and a read-only value rendered to match an input's height/weight so
   prescribed (non-editable) reps/weight read as values rather than plain text. */
.set-editor-label {
  font-size: 12px;
  color: var(--text-secondary);
  margin-bottom: 4px;
}
.set-editor-value {
  display: inline-block;
  font-size: 16px;
  font-weight: 600;
  padding: 8px 0;
}
.set-editor .input-inline { width: 100%; box-sizing: border-box; }

/* When the editor opens we scrollIntoView({block:'start'}) the edited row. On
   the Capacitor app (viewport-fit=cover) y=0 sits UNDER the device status bar
   (camera/clock), so leave a scroll-margin equal to the safe-area inset (plus a
   little breathing room) — the row then lands just below the status bar. In a
   normal browser env(safe-area-inset-top) is 0, so this is just the 16px gap. */
.set-edit-anchor { scroll-margin-top: calc(env(safe-area-inset-top) + 16px); }

/* Hide the native spinner arrows on number inputs everywhere — they look
   noisy at our compact input sizes, and on touch the text input plus the
   numeric keypad is enough. Standard cross-browser pattern. */
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
input[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
}

/* Error & info */
.error-msg {
  background: rgba(239, 68, 68, 0.15);
  color: var(--danger);
  padding: 12px;
  border-radius: var(--radius-sm);
  margin-bottom: 16px;
  font-size: 14px;
}

.info-msg {
  color: var(--text-secondary);
  font-size: 14px;
  text-align: center;
  padding: 32px 16px;
}

/* Badge */
.badge {
  display: inline-block;
  padding: 3px 8px;
  border-radius: 99px;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
}

.badge-primary { background: rgba(59, 130, 246, 0.2); color: var(--primary); }
/* The "Primary" (primary-lift) badge — a SOLID accent-danger pill (white on the
   scheme's paired danger colour) so the day's primary lift stands out sharply from
   the accent-tinted badges around it. Uses --accent-danger (the accent's paired
   danger, e.g. amber for charcoal) rather than the raw --danger red.
   (fix_primary_lift_badge_restyle_scope; fix_primary_badge_accent_danger) */
.badge-lead { background: var(--accent-danger); color: #fff; }
/* Home subscription-card call-to-action — a solid accent pill (matches .btn-primary),
   not the washed-out translucent badge it used to be. (owner 2026-07-03) */
.grace-cta { flex: none; white-space: nowrap; background: var(--primary); color: #fff; font-size: 13px; font-weight: 700; padding: 9px 15px; border-radius: 999px; }
.grace-banner:hover .grace-cta { background: var(--primary-dark); }
.badge-success { background: rgba(34, 197, 94, 0.2); color: var(--success); }
.badge-warning { background: rgba(245, 158, 11, 0.2); color: var(--warning); }
/* Secondary/tertiary accents were dropped (feat_accent_two_colour_schemes) — these
   legacy badges now tint from the single --primary accent. */
.badge-secondary { background: color-mix(in srgb, var(--primary) 20%, transparent); color: var(--primary); }
.badge-tertiary { background: color-mix(in srgb, var(--primary) 22%, transparent); color: var(--primary); }

/* Live banner while a background tenant data-migration runs (feat_data_migration_live_ux). */
.data-migration-banner {
  position: fixed;
  left: 50%;
  top: 12px;
  transform: translateX(-50%);
  z-index: 1000;
  max-width: calc(100% - 24px);
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
  border-radius: 99px;
  background: var(--primary);
  color: #fff;
  font-size: 14px;
  font-weight: 600;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
}
.data-migration-banner.done { background: var(--success, #22c55e); }
.data-migration-banner .dm-spinner {
  width: 14px;
  height: 14px;
  border: 2px solid rgba(255, 255, 255, 0.4);
  border-top-color: #fff;
  border-radius: 50%;
  animation: dm-spin 0.7s linear infinite;
}
@keyframes dm-spin { to { transform: rotate(360deg); } }

/* Inline "beta" tag for not-yet-fully-reviewed languages (feat_beta_language_badge). */
.beta-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 99px;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  background: rgba(245, 158, 11, 0.2);
  color: var(--warning);
  vertical-align: middle;
}
a.badge {
  text-decoration: none;
  vertical-align: middle;
  margin-left: 8px;
}

/* Link-as-badge: convention for any "link" affordance across the app — short
   inline action that isn't true back-navigation and doesn't deserve a full
   button. Apply with `.badge .badge-link` on a <button> or <a>. Examples:
   "Pick a program" on the log-only home, in-line cross-page jumps that
   used to be blue-underlined text. The .btn-back-arrow style stays
   reserved for actual back/history navigation (Settings / Progress / etc). */
.badge-link {
  background: var(--surface-2);
  color: var(--text-secondary);
  text-transform: none;
  letter-spacing: normal;
  font-size: 12px;
  padding: 6px 10px;
  cursor: pointer;
  border: 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  text-decoration: none;
}
.badge-link:hover { background: var(--surface); color: var(--text); }
.badge-link svg { display: block; width: 12px; height: 12px; }

/* Sets table */
.sets-table { width: 100%; border-collapse: collapse; }

.sets-table th {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-secondary);
  text-transform: uppercase;
  text-align: left;
  padding: 8px 4px;
  border-bottom: 1px solid var(--surface-2);
}

.sets-table td {
  padding: 10px 4px;
  font-size: 14px;
  border-bottom: 1px solid rgba(51, 65, 85, 0.5);
  vertical-align: middle;
}

.sets-table .amrap { color: var(--warning); font-weight: 600; }

/* Drop-set cascade tag (feat_set_technique_drop_sets) — a subtle pill marking the
   anchor + its lighter back-to-back drops. */
.drop-badge {
  display: inline-block;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--accent, var(--primary));
  background: color-mix(in srgb, var(--accent, var(--primary)) 14%, transparent);
  border-radius: 4px;
  padding: 1px 5px;
  vertical-align: middle;
}

/* Myo-reps cluster badge — a distinct hue from the drop-set badge (they share the .drop-badge markup
   because myo-reps reuse the DropGroupId rail). (feat_myo_reps_adhoc) */
.drop-badge.myo-badge {
  color: var(--tertiary, var(--secondary));
  background: color-mix(in srgb, var(--tertiary, var(--secondary)) 14%, transparent);
}

/* Aligned variant for the standard workout's Working Sets + assistance cards.
   Separate <table>s size their own columns under the default auto layout, so
   the Reps/Weight inputs bumped to different x-positions card-to-card. A fixed
   layout with the same per-column widths (all these tables share the 5-column
   Set / Target / Reps / Weight / Actions shape) lines every card up. Inputs
   fill their column instead of a fixed 70px so they never overflow a column. */
.sets-table-aligned { table-layout: fixed; }
.sets-table-aligned th:nth-child(1), .sets-table-aligned td:nth-child(1) { width: 11%; }
.sets-table-aligned th:nth-child(2), .sets-table-aligned td:nth-child(2) { width: 27%; }
.sets-table-aligned th:nth-child(3), .sets-table-aligned td:nth-child(3) { width: 18%; }
.sets-table-aligned th:nth-child(4), .sets-table-aligned td:nth-child(4) { width: 18%; }
.sets-table-aligned th:nth-child(5), .sets-table-aligned td:nth-child(5) { width: 26%; }
.sets-table-aligned .input-inline { width: 100%; min-width: 0; box-sizing: border-box; padding-left: 4px; padding-right: 4px; }

/* Workout card */
.workout-card {
  background: var(--surface);
  border-radius: var(--radius);
  padding: 14px 16px;
  margin-bottom: 8px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  cursor: pointer;
  transition: background 0.2s;
  text-decoration: none;
  color: var(--text);
}

.workout-card:hover { background: var(--surface-2); }

.workout-card-locked {
  opacity: 0.5;
  cursor: not-allowed;
}
.workout-card-locked:hover { background: var(--surface); }

.workout-card .lift-name { font-weight: 600; font-size: 15px; }
.workout-card .week-label { font-size: 13px; color: var(--text-secondary); }

/* Unified session list (home) — one continuous, tap-to-expand list of every
   session in the program. The row "card" is a <button> so it's fully
   keyboard-accessible; the expanded detail is its sibling. */
.session-cycle-header { margin: 18px 0 8px; font-size: 16px; }
.session-week-header {
  margin: 12px 0 6px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-secondary);
}
/* Minimisable grouping headers (cycle/wave/phase + week). Tap to collapse the
   section; the chevron rotates to ▸ when collapsed. */
.section-collapse { display: flex; align-items: center; gap: 6px; cursor: pointer; user-select: none; }
.section-collapse:focus-visible { outline: 2px solid var(--accent, var(--primary)); outline-offset: 2px; border-radius: 4px; }
.section-chevron { display: inline-block; color: var(--text-secondary); transition: transform 0.2s; font-size: 0.8em; }
.session-cycle.collapsed > .session-cycle-header .section-chevron,
.session-week.collapsed > .session-week-header .section-chevron { transform: rotate(-90deg); }
.session-cycle.collapsed > .session-week { display: none; }
.session-week.collapsed > .session-item { display: none; }
.session-week.collapsed > .session-feedback-entry { display: none; }
.session-item { margin-bottom: 8px; }

/* Premium badge — flags a Premium-only page/section (experiment 2026-06-14). */
.premium-badge {
  display: inline-block;
  vertical-align: middle;
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.02em;
  padding: 4px 8px;
  border-radius: 999px;
  color: var(--primary);
  background: color-mix(in srgb, var(--primary) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--primary) 35%, transparent);
  white-space: nowrap;
}

/* Inline per-week feedback entry — sits at the end of a completed week's
   sessions. Muted/dashed by default (optional on most programs); accent + solid
   when emphasised for RP. (user 2026-06-14) */
.session-feedback-entry {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
  padding: 10px 12px;
  border: 1px dashed var(--border);
  border-radius: 10px;
  background: var(--surface);
  color: inherit;
  text-decoration: none;
}
.session-feedback-entry:focus-visible { outline: 2px solid var(--accent, var(--primary)); outline-offset: 2px; }
.session-feedback-entry.is-rp { border-style: solid; border-color: var(--primary); }
.session-feedback-entry .sfe-icon {
  font-size: 16px;
  width: 22px;
  text-align: center;
  color: var(--text-secondary);
  flex: none;
}
.session-feedback-entry.is-rp .sfe-icon { color: var(--primary); }
.session-feedback-entry.is-done .sfe-icon { color: var(--success); }
.session-feedback-entry .sfe-text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.session-feedback-entry .sfe-label { font-weight: 600; }
.session-feedback-entry .sfe-chevron { color: var(--text-secondary); font-size: 18px; flex: none; }
/* When a session OR a block header is scrolled to the top on load, leave room
   for the fixed notifications bell (~40px) + the device status bar so it lands
   just below them, never tucked underneath. */
.session-item, .session-cycle, .session-week {
  scroll-margin-top: calc(env(safe-area-inset-top, 0px) + 60px);
}
.session-toggle {
  /* inherits .workout-card flex layout; a clickable div, not a <button> */
  width: 100%;
  margin-bottom: 0;
  text-align: left;
  align-items: center;
  gap: 10px;
}
.session-toggle:focus-visible { outline: 2px solid var(--accent, var(--primary)); outline-offset: 2px; }
.session-main { flex: 1; min-width: 0; }
.session-status { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
.session-chevron {
  display: inline-block;
  margin-top: 2px;
  color: var(--text-secondary);
  transition: transform 0.2s;
}
.session-item.expanded .session-chevron { transform: rotate(180deg); }
.session-item.expanded .session-toggle { background: var(--surface-2); }
.session-detail {
  background: var(--surface);
  border-radius: 0 0 var(--radius) var(--radius);
  margin-top: -4px;
}
.session-detail-inner { padding: 8px 16px 14px; }
.session-detail-msg { padding: 8px 0; margin: 0; }
.session-lift {
  /* Stacked: name + info tags on the first line, the reps/scheme on the second.
     The detail line wraps freely rather than being pinned to one nowrap row. */
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 7px 0;
  border-top: 1px solid var(--surface-2);
}
.session-detail-inner .session-lift:first-child { border-top: none; }
.session-lift-name { font-weight: 500; }
/* The "Main" badge sits inline after the lift name in the listing (collapsed title
   + expanded main-lift row) — nudge it onto the text's optical centre so it doesn't
   ride high against the heavier title. (feat_main_badge_in_listing) */
.session-main-badge { vertical-align: middle; margin-left: 4px; position: relative; top: -1px; }
.session-lift-detail { text-align: left; }
.session-locked-note { margin: 10px 0 0; }
/* Expanded-view action rows: each is [button] [efficacy warning]. The buttons (Start,
   ⛔ Skip, Continue, View) live here, not on the collapsed row. The first action row
   gets a hairline separator from the preview above. (feat_session_card_expand_actions) */
.session-action-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 10px; }
.session-preview:not(:empty) + .session-action-row { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--surface-2); }
.session-action { width: auto; flex: none; white-space: nowrap; }
/* Start + Skip laid out side by side, splitting the width equally. Warnings are
   deferred to confirm dialogs, so no inline note sits between them.
   (fix_session_start_skip_side_by_side_confirm) */
.session-action-row--split { flex-wrap: nowrap; }
.session-action-row--split .session-action { flex: 1 1 0; text-align: center; }
/* Reschedule-a-session control in the expanded listing (feat_reschedule_in_listing). */
/* The native <input type="date"> is kept (OS picker + change event) but visually hidden — the
   pencil beside the title date triggers it via showPicker(). (feat_reschedule_relay_and_pencil,
   feat_reschedule_pencil_single_date) */
.session-reschedule-input {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); border: 0; opacity: 0; pointer-events: none;
}
/* The reschedule pencil sits inline with the title date. */
.week-label .session-reschedule-pencil { vertical-align: middle; padding: 2px; min-height: 0; }
/* Only show the reschedule pencil on the expanded (up-next / in-progress) row — collapsed rows stay a
   clean scannable column. (owner 2026-07-10) */
.session-item:not(.expanded) .session-reschedule-pencil { display: none; }
/* The non-blocking efficacy note beside a Start/Skip button. */
.session-action-warn { font-size: 12px; line-height: 1.3; color: var(--warning); opacity: 0.9; flex: 1; min-width: 140px; }
.session-earlier-hint { text-align: center; margin: 2px 0 10px; }
.session-history-banner { margin-bottom: 12px; text-decoration: none; color: var(--text); }

/* Lift card */
.lift-card {
  background: var(--surface);
  border-radius: var(--radius);
  padding: 14px 16px;
  margin-bottom: 8px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.lift-card .lift-info { flex: 1; }
.lift-card .lift-name { font-weight: 600; font-size: 15px; }
.lift-card .lift-tm { font-size: 13px; color: var(--text-secondary); margin-top: 2px; }
.lift-card .lift-actions { display: flex; gap: 8px; }


/* Auth page */
.auth-page {
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  padding-top: 24px;
  position: relative;
  z-index: 1;
}

/* Sparks splash backdrop for the login / register routes.
   The image is fixed-positioned behind #app, covering the whole viewport.
   On narrow viewports we pin background-position to the right so the spark
   detail in the right half of the photo is what the user sees while the
   form column sits over the quieter left side. A dark gradient overlay
   keeps text legible. */
.auth-splash {
  position: fixed;
  inset: 0;
  background-image:
    linear-gradient(180deg, rgba(15,23,42,0.45) 0%, rgba(15,23,42,0.75) 60%, rgba(15,23,42,0.92) 100%),
    url('../assets/sparks.jpg');
  background-size: cover;
  background-position: right center;
  z-index: 0;
  pointer-events: none;
}

@media (min-width: 720px) {
  .auth-splash {
    background-position: center center;
  }
}

.auth-logo {
  display: block;
  margin: 0 auto 12px;
  width: auto;
  height: 96px;
  max-width: 100%;
  background: #fff;
  border-radius: 16px;
  padding: 8px;
  box-sizing: border-box;
}

.auth-page h1 {
  text-align: center;
  font-size: 32px;
  margin-bottom: 8px;
}

.auth-page .subtitle {
  text-align: center;
  color: var(--text-secondary);
  margin-bottom: 32px;
  font-size: 15px;
}

.auth-page .switch-link {
  text-align: center;
  margin-top: 16px;
  font-size: 14px;
  color: var(--text-secondary);
}

.auth-page .switch-link a {
  color: var(--primary);
  text-decoration: none;
}

/* The .auth-splash backdrop is ALWAYS a dark slate photo+gradient, independent of the
   active theme. So the loose text sitting directly on it (headings, subtitle, labels,
   switch/forgot links, dev-link copy) must stay light regardless of theme/accent — on a
   light theme the dark --text/--text-secondary and, worst of all, the default charcoal
   accent's dark-slate --primary link (#374151) render near-invisible on it. Inputs are
   deliberately excluded: they sit on their own --surface fill and keep the theme colours.
   (owner 2026-07-06: 'Forgot your password?' + other copy unreadable on charcoal.)
   SCOPED to `.auth-splash + .auth-page` (login/register/forgot/reset/verify) — the
   onboarding wizard reuses `.auth-page` WITHOUT a splash and sits on the theme bg, so it
   must keep the normal theme colours (owner 2026-07-06: wizard subtitle unreadable). */
.auth-splash + .auth-page h1 { color: #f8fafc; }
.auth-splash + .auth-page .subtitle,
.auth-splash + .auth-page .switch-link,
.auth-splash + .auth-page .form-group label,
.auth-splash + .auth-page .text-secondary { color: #cbd5e1; }
.auth-splash + .auth-page .switch-link a,
.auth-splash + .auth-page .text-secondary a { color: #f8fafc; text-decoration: underline; }

/* Setup wizard progress dots */
.setup-progress {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-bottom: 32px;
}

.progress-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--surface-2);
  transition: background 0.3s;
}

.progress-dot.done { background: var(--success); }
.progress-dot.active { background: var(--primary); }

/* Loading spinner */
.spinner {
  display: inline-block;
  width: 20px;
  height: 20px;
  border: 2px solid var(--border);
  border-top-color: var(--primary);
  border-radius: 50%;
  animation: spin 0.6s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

/* Modal overlay — scroll lives on the overlay itself so the scrollbar
   sits at the viewport's right edge (visually identical to the browser
   scrollbar) instead of as a mid-window strip inside the modal. The
   overlay is centred horizontally and starts from the top with padding,
   so a short modal still appears near the top of the viewport with a
   little breathing room and a tall modal scrolls with the page. */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 24px 0;
  overflow-y: auto;
  z-index: 200;
}

.modal {
  background: var(--surface);
  border-radius: var(--radius);
  padding: 24px 16px;
  width: 100%;
  max-width: 480px;
}

.modal h2 { margin-bottom: 16px; }
/* Optional leading icon (emoji or SVG) above the title on a confirm dialog. */
.modal-icon { font-size: 32px; line-height: 1; text-align: center; margin-bottom: 8px; }
.modal-icon + h2 { margin-top: 0; }

.modal-actions {
  display: flex;
  gap: 12px;
  margin-top: 20px;
}

.modal-actions .btn { flex: 1; }

/* ── Tap-to-expand read-only training cards ───────────────────────────────── */
/* Pinned ("Focus") editor mode: while logging, the active card sticks to the top
   of the screen and the rest of the page fades out, so the user's focus stays on
   the card they're entering — and the in-flow editor means no scroll-reposition
   listeners fighting the rest timer on close. (feat_training_day_pin_active_card_fade) */
.training-card-pinned {
  position: sticky;
  top: 8px;
  z-index: 6;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.4);
  border-color: var(--primary);
}
.logging-focus .training-card:not(.training-card-pinned) {
  opacity: 0.3;
  transition: opacity 0.2s ease;
}

/* Read-only summary cards on the workout day. .training-card-open marks a card
   whose body opens the input overlay on tap. */
.training-card-open { cursor: pointer; }
.training-card-open:hover { border-color: var(--primary); }
.training-card-open:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

/* Read-only summary table: fixed column layout so Set / Target / Logged line
   up cleanly across rows (and across cards). */
.sets-table-summary { table-layout: fixed; }
.sets-table-summary td, .sets-table-summary th { text-align: left; }
.sets-table-summary th:nth-child(1), .sets-table-summary td:nth-child(1) { width: 14%; }
.sets-table-summary th:nth-child(2), .sets-table-summary td:nth-child(2) { width: 48%; }
.sets-table-summary th:nth-child(3), .sets-table-summary td:nth-child(3) { width: 38%; }

/* Bigger, more readable text on the read-only training cards. */
.training-card h3 { font-size: 17px; }
.training-card .sets-table-summary td { font-size: 16px; padding-top: 11px; padding-bottom: 11px; }
.training-card .sets-table-summary th { font-size: 12px; }

/* The next loggable set's row is the tap target that reveals its editor. */
.set-row-open { cursor: pointer; }
.set-row-open > td:first-child { position: relative; }
.set-row-open:hover > td { background: var(--surface-2); }
.set-row-open:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
.set-row-selected > td { background: var(--surface-2); }

/* The CURRENT set to log — persistently highlighted (tint + left accent) so it's
   obvious where you are in the workout, replacing the old "Tap to log ›" hint. */
.set-row-next > td { background: color-mix(in srgb, var(--primary) 12%, transparent); }
.set-row-next > td:first-child { box-shadow: inset 3px 0 0 var(--primary); }
/* Explicit "Log" affordance shown in the current row's logged-value column. */
.log-pill {
  display: inline-block;
  background: var(--primary);
  color: #fff;
  font-size: 13px;
  font-weight: 600;
  padding: 3px 12px;
  border-radius: 999px;
  white-space: nowrap;
}
/* The injected accordion edit row spans the table; strip the cell chrome so the
   editor card sits flush under the selected row. */
.set-edit-tr > td { padding: 4px 0 8px; border: none; }

.log-progress { font-size: 13px; color: var(--text-secondary); }
.log-progress-done { color: var(--success); font-weight: 600; }

/* Editor layout toggle (Floating / Inline) — temporary comparison control. */
.log-editor-mode { display: flex; align-items: center; gap: 8px; margin: 0 0 12px 2px; }

/* Input overlay (reuses .modal-overlay / .modal). Scrollable, generous touch
   targets, full editable table inside. */
.log-overlay { align-items: flex-start; z-index: 250; }
.log-overlay-modal { max-width: 520px; max-height: calc(100vh - 48px); display: flex; flex-direction: column; }
.log-overlay-body { overflow-y: auto; }
.log-overlay-body .input-inline { min-height: 40px; }
.log-overlay-body .sets-table td, .log-overlay-body .sets-table th { padding-top: 10px; padding-bottom: 10px; }
.log-overlay-close {
  background: none;
  border: none;
  color: var(--text-secondary);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  padding: 4px 8px;
  width: auto;
}
.log-overlay-close:hover { color: var(--text); }

/* Per-set editor (editorShell) — drops under the SELECTED set ROW. Two layout
   modes: .log-editor-overlay floats over the rows below (positionLogEditor
   anchors it to the row — no page jump); .log-editor-inline sits in flow inside
   the injected accordion edit-row. */
/* The inline + floating set-entry form needs a visible edge in EVERY theme.
   Previously only high-contrast drew one (via its [data-theme] .card border);
   in dark/light the editor blended into the rows behind it (user 2026-06-03). */
.log-editor-card { margin: 0; border: 1px solid var(--border); }
.log-editor-body .input-inline { min-height: 40px; }
.log-editor-body .sets-table td, .log-editor-body .sets-table th { padding-top: 10px; padding-bottom: 10px; }
.log-editor-inline { margin: 0; }
.log-editor-overlay {
  position: absolute;
  z-index: 50;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
}

/* Page-style wizard — same DOM shape as a .modal-overlay + .modal pair, but
   renders inline as a regular full-width section instead of a fixed dark
   overlay. Used by 1800 to convert the Start-a-Program "modal" into a
   page-style wizard while keeping its internal markup untouched. The class
   is applied on the .modal-overlay element by home.js render code.
*/
.modal-overlay.as-page {
  position: static;
  inset: auto;
  background: transparent;
  padding: 0;
  overflow: visible;
  z-index: auto;
}
.modal-overlay.as-page > .modal {
  background: transparent;
  border-radius: 0;
  padding: 0;
  max-width: none;
}

/* Start-block wizard */
.wizard-progress {
  display: flex;
  gap: 6px;
  margin-bottom: 8px;
}
.wizard-progress .wizard-dot {
  flex: 1;
  height: 4px;
  border-radius: 2px;
  background: var(--border);
}
.wizard-progress .wizard-dot.active { background: var(--primary); }
.wizard-step-label {
  font-size: 12px;
  color: var(--text-secondary);
  margin-bottom: 14px;
}
.wizard-step { display: none; }
.wizard-step.active { display: block; }

/* Section spacing */
.section { margin-bottom: 24px; }
.section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}

/* Settings: small heading above a group of accordion .section-card rows. */
.settings-group-heading {
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-secondary);
  margin: 24px 0 8px 4px;
}
.settings-group-heading:first-child { margin-top: 0; }

/* Divider */
.divider {
  height: 1px;
  background: var(--surface-2);
  margin: 16px 0;
}

/* Flex utils */
.flex { display: flex; }
.flex-between { display: flex; justify-content: space-between; align-items: center; }
.gap-8 { gap: 8px; }
.gap-12 { gap: 12px; }
.mt-8 { margin-top: 8px; }
.mt-16 { margin-top: 16px; }
.mb-16 { margin-bottom: 16px; }
.text-center { text-align: center; }
.text-secondary { color: var(--text-secondary); }
.text-sm { font-size: 13px; }
.text-xs { font-size: 11px; }
.text-lg { font-size: 17px; }

/* Mobile readability bump (1780).
   Most font-sizes in this file are in px, so the desktop browser values
   (which the user explicitly described as "fine") are preserved and a
   targeted media query bumps the smallest sizes up by 1-2px when the
   viewport is phone-class. Anything ≥ 16px is left alone — those are
   already comfortable on mobile. We DON'T scale the html font-size
   because there are no rem/em-based rules to inherit from it; cascading
   via @media is the actual lever.
*/
@media (max-width: 480px) {
  body { font-size: 16px; }
  .text-xs { font-size: 13px; }
  .text-sm, .week-label, .lift-tm,
  .workout-card .week-label, .lift-card .lift-tm { font-size: 14px; }
  .btn-sm { font-size: 14px; }
  .tab { font-size: 12px; }
  h3 { font-size: 16px; }
}

/* Settings tabs */
.settings-tabs {
  display: flex;
  background: var(--surface);
  border-radius: var(--radius-sm);
  padding: 3px;
  margin-bottom: 16px;
  gap: 3px;
}

.settings-tab {
  flex: 1;
  padding: 10px;
  border: none;
  background: none;
  color: var(--text-secondary);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  border-radius: 6px;
  transition: all 0.2s;
}

.settings-tab.active {
  background: var(--primary);
  color: #fff;
}

/* Help icon */
.help-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* The glyph IS the circled-i (ⓘ U+24D8) — no CSS circle, just colour it. */
  color: var(--primary);
  font-size: 17px;
  line-height: 1;
  text-decoration: none;
  margin-left: 4px;
  vertical-align: middle;
  flex-shrink: 0;
}

.help-icon:hover { color: var(--primary-dark); }

/* Help page content */
.help-content p { margin-bottom: 12px; line-height: 1.6; font-size: 15px; }
.help-content h3 { margin-top: 20px; margin-bottom: 8px; }
.help-content ul { margin-bottom: 12px; padding-left: 20px; }
.help-content li { margin-bottom: 6px; line-height: 1.5; font-size: 15px; }
.help-content a { color: var(--link); }
.help-content table { margin-bottom: 16px; }

/* Rest timer overlay */
/* While a full rest-timer overlay is up, lock the page behind it so scrolling the
   overlay (or a stray touch) can't scroll the log page underneath. Released when the
   overlay is minimised to the floating chip. (fix_timer_overlay_scroll_lock) */
html.rest-timer-scroll-lock, html.rest-timer-scroll-lock body { overflow: hidden; }

.rest-timer-overlay {
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* `safe center` keeps the content top-aligned + SCROLLABLE when the keyboard
     shrinks the viewport (plain `center` traps the overflow), and overflow-y lets
     the focused input scroll above the keyboard — so the ring shifts up instead
     of being squeezed. (bug: keyboard shrinks the timer ring) */
  justify-content: safe center;
  overflow-y: auto;
  gap: 24px;
  padding: 16px 0;
  background: var(--scrim);
  color: var(--text);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  z-index: 300;
  pointer-events: auto;
}
/* The ring keeps its size when the viewport shrinks (keyboard) — never squeezed. */
.rest-timer-ring { flex-shrink: 0; }
/* Train surface (has-log): top-anchor instead of vertically centring, so the
   ring keeps a FIXED position when the area below it swaps between the up-next
   line (while counting) and the taller log form (at GO!). Centring made the
   whole stack re-centre on that height change → the circle "jumped". The
   non-has-log timers (manual rest, session-end) stay centred. (user 2026-06-10) */
.rest-timer-overlay.has-log {
  justify-content: flex-start;
  /* Top-pad so the RING CENTRE lands ~40% down the viewport (user 2026-06-11).
     Above the ring sits the header (~70px) + the 24px column gap, and the ring
     radius is 120px → centre ≈ padding-top + 214px. Solve for 40vh; floor at
     24px so short screens don't push the controls off-screen. */
  padding: max(24px, calc(40vh - 214px)) 16px 24px;
}
/* The below-ring wrapper is TRANSPARENT in portrait — its children (next / log /
   tabs) behave as direct overlay flex items exactly as before. (Landscape rule below
   turns it into a real side column.) (fix_timer_landscape_rotation) */
.rest-timer-below { display: contents; }

/* Landscape on a short viewport (phones turned sideways): lay the ring and the
   area-below-it SIDE BY SIDE instead of stacked, using the wider aspect ratio.
   Header spans the top; ring bottom-left; the below-wrapper becomes a scrollable
   column bottom-right. Portrait + tall screens (tablets/desktop) are untouched.
   (user 2026-06-24 — fix_timer_landscape_rotation) */
@media (orientation: landscape) and (max-height: 600px) {
  .rest-timer-overlay.has-log {
    display: grid;
    grid-template-columns: auto minmax(240px, 380px);
    grid-template-areas: "head head" "ring below";
    align-content: center;
    justify-content: center;
    column-gap: 32px;
    row-gap: 10px;
    padding: 12px 24px;
    overflow-y: auto;
  }
  .rest-timer-overlay.has-log > .rest-timer-header { grid-area: head; justify-self: center; }
  /* Nudge the ring up slightly off dead-centre so it sits a touch higher in the landscape
     side-by-side layout (owner 2026-07-06). translateY keeps the grid cell size unchanged. */
  .rest-timer-overlay.has-log > .rest-timer-ring { grid-area: ring; align-self: center; transform: translateY(-12px); }
  .rest-timer-overlay.has-log > .rest-timer-below {
    grid-area: below;
    align-self: center;
    display: flex;
    flex-direction: column;
    /* Centre the (max-width-capped) next/log/tabs within the column — without this
       they pin to the column's left edge, so the skip/done buttons sit off-centre
       above the tabs. Matches the portrait overlay's align-items:center.
       (fix_timer_landscape_button_alignment) */
    align-items: center;
    gap: 12px;
    min-width: 0;
    max-height: 92vh;
    overflow-y: auto;
  }
  /* The eta chip stays pinned top-left; the dismiss stays top-right. */
  .rest-timer-overlay.has-log > .rest-timer-dismiss { position: fixed; top: 10px; right: 14px; }
}
.rest-timer-next {
  text-align: center;
  background: var(--surface);
  border: 1px solid var(--surface-2);
  border-radius: var(--radius);
  padding: 14px 22px;
  max-width: 320px;
}
.rest-timer-next-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--text-secondary);
  margin-bottom: 4px;
}
/* "Today's session" line-up on the workout start overlay — names only, no
   reps/weight, so the lifter sees what's coming. (user 2026-06-10) */
.rest-timer-day-list {
  width: 100%;
  max-width: 320px;
  background: var(--surface);
  border: 1px solid var(--surface-2);
  border-radius: var(--radius);
  padding: 14px 18px;
}
.rest-timer-day-list-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--text-secondary);
  margin-bottom: 8px;
}
.rest-timer-day-list-items {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.rest-timer-day-list-items li { font-size: 15px; font-weight: 600; color: var(--text); }
/* Manual numbering (connector rows must not shift the count). */
.rest-timer-day-list-items .day-item { display: flex; align-items: baseline; gap: 8px; }
.rest-timer-day-list-items .day-item-num {
  min-width: 16px; text-align: right; color: var(--text-secondary);
  font-variant-numeric: tabular-nums; font-weight: 600;
}
/* Superset offer / join row, sits between two line-up items, indented under the names.
   Negative margin pulls it tight to the pair (counteracting the list's 4px gap) so it
   doesn't float in too much space. (owner 2026-07-04) */
.rest-timer-day-list-items .day-ss { margin: -3px 0 -3px 24px; }
.rest-timer-day-list-items .day-ss-btn {
  background: none; border: none; cursor: pointer; padding: 0;
  font-size: 12px; font-weight: 700; letter-spacing: 0.02em;
  display: inline-flex; align-items: center; gap: 6px;
}
/* Grouped-set OFFER pill (Superset / Compound / Tri-set / Giant set) — ONE consistent affordance:
   a rounded accent-outlined pill, transparent interior, no faint line/fill behind. (fix_grouped_set_offer_pill_style) */
.rest-timer-day-list-items .day-ss-offer {
  color: var(--primary);
  border: 1px solid var(--primary);
  border-radius: 999px;
  background: transparent;
  padding: 2px 10px; /* symmetric → label centred in the fixed line box (fix_offer_pill_vcenter) */
  /* FIXED line box so every offer pill is the SAME height: the ⮂ superset glyph isn't in the primary
     font and falls back to a taller-metric font, which otherwise stretched that pill ~1px vs the '+'
     pills. A fixed line-height pins the strut regardless of glyph/font. (fix_offer_pill_lineheight) */
  line-height: 15px;
}
/* No case change on select — keep it lower-case like the offer, in the selected accent. */
.rest-timer-day-list-items .day-ss-tag {
  letter-spacing: 0.06em; font-size: 11px; color: var(--primary);
}
.rest-timer-day-list-items .day-ss-unjoin { color: var(--text-secondary); }
.rest-timer-day-list-items .day-ss-joined > .day-ss-tag {
  display: inline-block; letter-spacing: 0.06em; font-size: 11px; color: var(--primary);
}
/* JOINED superset pair rail: a continuous 3px accent bar down the left edge of the pair
   (both name rows + the connector between), matching the program listing. The connector's
   negative vertical margin eats the list's 4px gap so the three segments touch (and drops
   the 24px indent so the bar stays flush left). (owner 2026-07-04) */
.rest-timer-day-list-items .day-railed { border-left: 3px solid var(--primary); padding-left: 8px; }
.rest-timer-day-list-items .day-ss.day-railed { margin: -4px 0; }
.rest-timer-day-list-items .day-rail-first { border-top-left-radius: var(--radius-sm, 6px); }
.rest-timer-day-list-items .day-rail-last { border-bottom-left-radius: var(--radius-sm, 6px); }
.rest-timer-next-detail {
  font-size: 17px;
  font-weight: 700;
  color: var(--text);
}
.rest-timer-ring {
  position: relative;
  width: 240px;
  height: 240px;
  display: flex;
  align-items: center;
  justify-content: center;
  filter: drop-shadow(0 0 24px rgba(0, 0, 0, 0.4));
}
.rest-timer-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.rest-timer-track {
  fill: none;
  stroke: var(--ring-track);
  stroke-width: 1.5;
}
.rest-timer-progress {
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  transition: stroke-dashoffset 1s linear, stroke 0.3s;
}
.rest-timer-ring.red    .rest-timer-progress { stroke: var(--phase-red); }
.rest-timer-ring.orange .rest-timer-progress { stroke: var(--phase-orange); }
.rest-timer-ring.yellow .rest-timer-progress { stroke: var(--phase-yellow); }
.rest-timer-ring.green  .rest-timer-progress { stroke: var(--phase-green); }
.rest-timer-ring.red    .rest-timer-text { color: var(--phase-red); }
.rest-timer-ring.orange .rest-timer-text { color: var(--phase-orange); }
.rest-timer-ring.yellow .rest-timer-text { color: var(--phase-yellow); }
.rest-timer-ring.green  .rest-timer-text { color: var(--phase-green); }
.rest-timer-inner {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}
.rest-timer-stage {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 3px;
  text-transform: uppercase;
  min-height: 16px;
  color: inherit;
  opacity: 0.85;
}
.rest-timer-text {
  position: relative;
  font-size: 56px;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  letter-spacing: -1px;
  transition: color 0.3s;
  line-height: 1;
}
/* PAUSED ring (showing the lift, not a countdown): one line "10 × 25" — the ×
   sits between reps and weight, all the same colour. The reps-only/no-weight case
   shows a small "REPS" label over the rep count. Scoped to .paused so the live
   countdown time keeps its big 56px. (user 2026-06-09) */
.rest-timer-overlay.paused .rest-timer-stage {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 2px;
  opacity: 0.85;
  min-height: 0;
}
/* Paused "lift this" ring: weight stacked ABOVE reps, both the SAME font, at the
   countdown's weight (400) so every timer-circle display matches. (user 2026-06-10) */
.rest-timer-overlay.paused .rest-timer-text {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  font-size: 30px;
  font-weight: 400;
  letter-spacing: 0;
  line-height: 1.1;
}
.rest-timer-overlay.paused .rest-timer-text .rt-ring-weight,
.rest-timer-overlay.paused .rest-timer-text .rt-ring-reps { font-size: inherit; font-weight: inherit; line-height: 1.1; }
/* The unit (kg/lb) rides small alongside the weight inside the ring. */
.rest-timer-overlay.paused .rt-ring-unit { font-size: 0.5em; font-weight: 400; opacity: 0.75; }
/* Hold stopwatch (feat_hold_stopwatch_replaces_timer_ring): the count-up time is the focal number,
   so keep it at the countdown's big size — the paused "lift this" 30px is too small here. */
.rest-timer-overlay.hold.paused .rest-timer-text {
  font-size: 56px;
  font-weight: 400;
  letter-spacing: -1px;
}
/* The stopwatch controls are injected up-front but stay hidden through the rest countdown; they appear
   only once the ring flips to the stopwatch at GO! (the overlay gains .hold). Start/Stop sits below the
   ring; the delay/sound management lives in the Controls tab. */
.hold-stopwatch-toggle, .hold-stopwatch-manage { display: none; }
.rest-timer-overlay.hold .hold-stopwatch-toggle { display: flex; justify-content: center; margin: 4px 0 2px; }
.rest-timer-overlay.hold .hold-stopwatch-manage { display: flex; align-items: center; justify-content: center; gap: 16px; flex-wrap: wrap; padding: 6px 0; }
.rest-timer-dismiss {
  position: absolute;
  top: calc(16px + env(safe-area-inset-top));
  right: calc(16px + env(safe-area-inset-right));
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--surface-2);
  color: var(--text);
  font-size: 18px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* "Time left" chip — top-left of the timer surface, IN PLACE (not the floating
   minimised chip). A small ring whose border slowly drains through the session +
   the estimated minutes remaining. (user 2026-06-10) */
.rest-timer-eta {
  position: absolute;
  top: calc(14px + env(safe-area-inset-top));
  left: calc(14px + env(safe-area-inset-left));
  width: 56px;
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.rest-timer-eta-svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.rest-timer-eta-track { fill: none; stroke: var(--ring-track); stroke-width: 2.5; }
.rest-timer-eta-prog { fill: none; stroke: var(--text-secondary); stroke-width: 2.5; stroke-linecap: round; opacity: 0.7; }
.rest-timer-eta-label { display: flex; align-items: baseline; gap: 1px; color: var(--text-secondary); line-height: 1; }
.rest-timer-eta-min { font-size: 17px; font-weight: 700; font-variant-numeric: tabular-nums; }
.rest-timer-eta-unit { font-size: 11px; font-weight: 600; }

/* Maximised-timer controls: ±30s nudge row + a 1/2/3/5/8/10-minute preset grid.
   Icon-based, generously spaced. Hidden in the minimized state (rule below). */
.rest-timer-quick-picks {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  margin: 0 auto;
  width: 100%;
  max-width: 320px;
}
.rest-timer-adjust { display: flex; gap: 14px; justify-content: center; }
.rest-timer-adjust-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 10px 20px;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--surface-2);
  color: var(--text);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.rest-timer-adjust-btn svg { width: 18px; height: 18px; }
.rest-timer-adjust-btn:active { background: var(--surface-2); }
.rest-timer-presets {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  width: 100%;
}
.rest-timer-preset {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 12px 0;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--surface-2);
  color: var(--text);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.rest-timer-preset svg { width: 20px; height: 20px; opacity: 0.8; }
.rest-timer-preset:active { background: var(--surface-2); }

/* Minimized state — small ring at bottom-right, lets the page beneath stay interactive */
/* Minimised rest timer: the floating corner chip is RETIRED — the centre toolbar
   button is now the minimised affordance (feat_toolbar_mini_timer_button). So a
   minimised overlay simply hides; tapping the toolbar's mini-timer maximises it.
   (The `.minimized .rest-timer-*` child rules below are inert while hidden — kept
   for now; a later stage can prune them.) */
.rest-timer-overlay.minimized {
  display: none;
}
.rest-timer-overlay.minimized .rest-timer-ring {
  /* Match the floating chip exactly (56px) so minimising reads as the same
     chip in the same slot — no size jump. (feat_timer_minimised_time_chip_size) */
  width: 56px;
  height: 56px;
  pointer-events: auto;
  cursor: pointer;
  background: var(--surface);
  border-radius: 50%;
  filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.35));
  /* Let the pointer-event drag handler move the chip on touchscreens: without
     this, the browser claims the touch for scrolling and fires pointercancel
     (never pointermove), so the chip wouldn't drag on touch. Tap-to-maximise
     still works (click fires on tap regardless). */
  touch-action: none;
}
/* Floating workout tools — draggable chips (look like the minimised timer): the
   rest-timer start button + the stopwatch. z-index below the timer overlay (300).
   Default corners are bottom-left, clear of the tab bar + the minimised timer
   chip (bottom-right); the stopwatch sits directly above the start button. */
.floating-tool {
  position: fixed;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--surface-2);
  color: var(--primary);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
  z-index: 150;
  cursor: pointer;
  touch-action: none;
  transition: opacity 0.2s;
}
.floating-tool svg { width: 16px; height: 16px; opacity: 0.85; }
.floating-tool-time { font-size: 15px; font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; }
/* Both default to the bottom-right, ABOVE the tab bar: the rest-start (hourglass)
   button shares the slot with the minimised rest timer (only one shows at a
   time); the stopwatch sits directly above it, close (user 2026-06-06). */
#rest-start-btn { right: 16px; bottom: 96px; }
#rest-start-btn svg { width: 26px; height: 26px; opacity: 1; }
#stopwatch-btn { right: 16px; bottom: 158px; }
#stopwatch-btn svg { width: 24px; height: 24px; opacity: 1; }
#plate-calc-btn { right: 16px; bottom: 220px; }
#plate-calc-btn svg { width: 24px; height: 24px; opacity: 1; }
/* Add-supplement "+" chip on the program page — same bottom-right slot the rest
   timer uses on the workout page (the two pages never show both). */
#supplement-chip { right: 16px; bottom: 96px; }
#supplement-chip svg { width: 26px; height: 26px; opacity: 1; }
/* Fade the chips while the page is scrolling so they don't obscure content. */
.tools-scrolling .floating-tool { opacity: 0.12; }

/* ── Supplements page forms ──────────────────────────────────────────────────
   .form-row / .form-actions were used by supplements.js but never styled, so the
   add form's spacing read poorly. Define them + the repeatable reminder rows. */
.form-row { display: flex; gap: 12px; flex-wrap: wrap; }
.form-row > .form-group { flex: 1; min-width: 140px; margin-bottom: 0; }
.form-actions { margin-top: 16px; display: flex; gap: 8px; }
.reminders-section { margin-top: 16px; }
.reminders-list { display: flex; flex-direction: column; gap: 10px; margin: 8px 0; }
.reminder-row {
  border: 1px solid var(--surface-2);
  border-radius: var(--radius-sm);
  padding: 10px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: var(--surface);
}
.reminder-row-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
/* Trigger pills (Time of day / Every n days / After a workout) prefer one line:
   no wrap, share the row width, shrink to fit. (user 2026-06-15) */
.rem-trigger { display: flex; gap: 6px; flex: 1; min-width: 0; }
.rem-trigger .rem-trig {
  font-size: 11px; padding: 4px 8px;
  flex: 1 1 auto; white-space: nowrap; text-align: center;
}
.rem-time-fields { display: flex; flex-direction: column; gap: 8px; }
/* Theme the reminder inputs — they live outside .form-group, so without this the
   time picker + message box render as unstyled white controls on the dark theme
   (the "drop-downs are wrong" report). (user 2026-06-14) */
.reminder-row input[type="time"],
.reminder-row input[type="date"],
.reminder-row input[type="number"],
.reminder-row input[type="text"] {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  padding: 8px 10px;
  font-size: 14px;
  color-scheme: dark light; /* native time/date picker chrome follows the theme */
}
.reminder-row input:focus { border-color: var(--primary); outline: none; }
.rem-time-fields .rem-time { width: 140px; }
.reminder-row .rem-msg { width: 100%; }
.reminder-row .rem-remove {
  background: none; border: none; color: var(--text-secondary);
  font-size: 16px; line-height: 1; cursor: pointer; padding: 4px 8px;
}
.reminder-days { display: flex; gap: 6px; flex-wrap: wrap; }
.reminder-days .rem-day { width: 38px; }
/* "Every N days" fields — N + start date sit on one wrapping row, each label
   stacked above its input to match .form-group. (user 2026-06-15) */
.rem-interval-fields { display: flex; gap: 12px; flex-wrap: wrap; }
.rem-interval-label {
  display: flex; flex-direction: column; gap: 4px;
  font-size: 12px; color: var(--text-secondary);
}
.rem-interval-fields .rem-interval { width: 90px; }
.rem-interval-fields .rem-start { width: 170px; }
.reminders-hint { margin-top: 4px; }

/* Per-supplement edit / history panel — give it room when populated so the
   form doesn't sit flush against the card edge (the "camped" edit view).
   Empty (closed) → no chrome. (user 2026-06-15) */
.sup-detail:not(:empty) {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--surface-2);
}

/* Full-screen cardio timing screen (opened from the stopwatch chip) — modelled on
   the rest-timer screen but the ring counts UP. Scrollable (it carries the cardio
   config form). */
.cardio-screen-overlay {
  position: fixed;
  inset: 0;
  z-index: 300;
  background: var(--scrim);
  color: var(--text);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  overflow-y: auto;
  display: flex;
  justify-content: center;
  padding: calc(16px + env(safe-area-inset-top)) 16px 96px;
}
/* Content follows the app theme (dark / light / high-contrast) — same as the
   rest timer. */
.cardio-screen-overlay h2 { color: var(--text); }
.cardio-screen-overlay label { color: var(--text-secondary); }
.cardio-screen-overlay input,
.cardio-screen-overlay select {
  width: 100%;
  padding: 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  font-size: 15px;
}
.cardio-screen-overlay .log-overlay-close { color: var(--text-secondary); }
.cardio-screen-inner { width: 100%; max-width: 420px; display: flex; flex-direction: column; align-items: center; gap: 16px; }
.cardio-screen-head { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.cardio-screen-ring { position: relative; width: 220px; height: 220px; cursor: pointer; }
.cardio-screen-ring svg { width: 220px; height: 220px; }
/* Match the rest-timer ring: faint track, thin bright fill. */
.cardio-screen-ring .ring-track { stroke: var(--ring-track); }
.cardio-screen-ring .ring-fill { stroke: var(--phase-green); transition: stroke-dashoffset 0.25s linear, stroke 0.3s ease; }
/* Pace colour vs the ghost targets (feat_cardio_timer_colour_by_pace). */
.cardio-screen-ring.pace-red .ring-fill { stroke: var(--phase-red); }
.cardio-screen-ring.pace-amber .ring-fill { stroke: var(--phase-orange); }
.cardio-screen-ring.pace-green .ring-fill { stroke: var(--phase-green); }
.cardio-screen-time {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-size: 44px; font-weight: 400; font-variant-numeric: tabular-nums; letter-spacing: -1px; color: var(--text);
}
.cardio-screen-controls { display: flex; gap: 10px; }
/* Ghost pace targets (previous + best) shown under the count-up ring. */
.cardio-ghosts { display: flex; gap: 18px; justify-content: center; flex-wrap: wrap; min-height: 18px; }
.cardio-ghost { font-size: 14px; font-variant-numeric: tabular-nums; color: var(--text-secondary); }
.cardio-ghost-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-secondary); margin-right: 2px; }
.cardio-screen-form { width: 100%; }
.cardio-stay-awake { display: flex; align-items: center; gap: 8px; font-size: 15px; }
.cardio-stay-awake input { width: 18px; height: 18px; flex: none; }
.cardio-advanced { width: 100%; margin-top: 4px; }
.cardio-advanced > summary { cursor: pointer; padding: 8px 0; color: var(--text); font-weight: 600; }

/* Minimised cardio screen — only the ring shows, parked bottom-right above the
   tab bar (shares the corner with the now-hidden stopwatch chip). */
.cardio-screen-overlay.minimized {
  inset: auto; bottom: 76px; right: 16px; padding: 0;
  background: transparent; backdrop-filter: none; -webkit-backdrop-filter: none;
  overflow: visible; display: block;
}
.cardio-screen-overlay.minimized .cardio-screen-inner { width: auto; gap: 0; }
.cardio-screen-overlay.minimized .cardio-screen-head,
.cardio-screen-overlay.minimized .cardio-screen-controls,
.cardio-screen-overlay.minimized .cardio-ghosts,
.cardio-screen-overlay.minimized .cardio-screen-form { display: none; }
.cardio-screen-overlay.minimized .cardio-screen-ring {
  /* Match the floating chip exactly (56px) — see rest-timer note above. */
  width: 56px; height: 56px; background: var(--surface); border-radius: 50%;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
}
.cardio-screen-overlay.minimized .cardio-screen-ring svg { width: 56px; height: 56px; }
.cardio-screen-overlay.minimized .cardio-screen-time { font-size: 15px; }

.rest-timer-overlay.minimized .rest-timer-text { font-size: 15px; }
/* The minimised chip's countdown reads as a clear slim DECREASING border hugging
   the chip edge. The base ring (stroke 2, r=44 in a 100-unit viewBox) renders as a
   faint hairline INSET from the 56px edge — easy to miss, and in dark mode the
   faint track vanishes so only a stub arc shows. Fix: scale the SVG out so the ring
   sits at the edge (scale preserves the dash ratios, so no offset math breaks),
   thicken the colored arc, and give the track enough contrast to always show the
   full border outline that the colour drains around. (user 2026-06-13) */
.rest-timer-overlay.minimized .rest-timer-svg { transform: scale(1.07); }
.rest-timer-overlay.minimized .rest-timer-progress { stroke-width: 6; }
.rest-timer-overlay.minimized .rest-timer-track { stroke-width: 6; stroke: var(--text-secondary); opacity: 0.35; }
.rest-timer-overlay.minimized .rest-timer-header,
.rest-timer-overlay.minimized .rest-timer-stage,
.rest-timer-overlay.minimized .rest-timer-next,
.rest-timer-overlay.minimized .rest-timer-log,
.rest-timer-overlay.minimized .rest-timer-quick-picks,
/* The whole tab strip (Controls / Plates / Today / History bar + panels) collapses
   too — minimised shows ONLY the clock circle, nothing else (user 2026-06-24: "the
   menu and plate calculator is showing"). */
.rest-timer-overlay.minimized .rest-timer-tabs,
.rest-timer-overlay.minimized .rest-timer-eta,
.rest-timer-overlay.minimized .rest-timer-dismiss { display: none; }

/* Lift name (large) above the ring throughout the workout, with the warm-up
   step count ("Warm-up 1 of 4") as a subtitle. (user 2026-06-09) */
.rest-timer-header { text-align: center; }
.rest-timer-lift-name { font-size: 30px; font-weight: 800; line-height: 1.1; }
.rest-timer-sub { font-size: 13px; font-weight: 700; letter-spacing: 1.5px; text-transform: uppercase; color: var(--text-secondary); margin-top: 4px; }

/* ── Inline logging panel on the rest timer ────────────────────────────────
   Log the next set straight from the timer page — reps/weight + tick/cross and,
   for accessories, a "don't increase weight" hold (user 2026-06-08). */
.rest-timer-log {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  margin-top: 18px;
  width: 100%;
  max-width: 320px;
}
.rest-timer-log-inputs { display: flex; gap: 14px; justify-content: center; }
.rest-timer-log-field { display: flex; flex-direction: column; gap: 4px; align-items: center; }
.rest-timer-log-field > span { font-size: 12px; color: var(--text-secondary); font-weight: 600; }
/* Lift-mode reps/weight read at the same size as the warm-up step's big target
   (.rest-timer-log-warmup-target, 26px) — they used to be smaller. (user 2026-06-09) */
.rest-timer-log-field input { width: 100px; text-align: center; font-size: 26px; font-weight: 700; }
.rest-timer-log-field .set-editor-value { font-size: 26px; font-weight: 700; padding: 0; }
/* Placeholders (the rep range / "?") sit paler than typed values. (user 2026-06-10) */
.rest-timer-log-field input::placeholder { color: var(--text-secondary); opacity: 0.5; font-weight: 600; }
/* Weight unit (kg/lb) rides to the right of the weight input. (user 2026-06-10) */
.rest-timer-log-weight-wrap { display: inline-flex; align-items: baseline; gap: 5px; }
.rest-timer-log-unit { font-size: 14px; font-weight: 600; color: var(--text-secondary); }
.rest-timer-log-hold {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  color: var(--text);
}
.rest-timer-log-hold input { width: 18px; height: 18px; }
.rest-timer-log-actions { display: flex; gap: 12px; align-items: center; justify-content: center; width: 100%; }
.rest-timer-log-actions .rt-log-confirm { flex: 1; padding: 12px 0; font-size: 16px; font-weight: 700; letter-spacing: 1px; }
.rest-timer-log-actions .rt-log-skip { padding: 12px 16px; }
/* Persistent train surface (has-log). While a WORKING (non-warm-up) rest is
   COUNTING, show the full timer controls (±30s adjust AND the duration presets)
   and HIDE the log form — the log buttons only become available once the rest
   hits GO! and the ring flips to "lift this" (paused). Warm-up rests are fixed at
   60s, so they never show the timer controls. (user 2026-06-09) */
.rest-timer-overlay.has-log .rest-timer-quick-picks { display: none; }
.rest-timer-overlay.has-log .rest-timer-presets { display: none; }
/* The timer controls (±30s adjust + duration presets) show ONLY while the rest is
   COUNTING — for working sets AND warm-up rests (fix_warmup_timer_today_and_controls:
   warm-up rests get their controls back so a warm-up rest can be extended/shortened).
   Once the ring flips to "lift this" (paused) and the Log form appears, the controls
   hide so the entry form owns the surface — adjusting rest mid-entry makes no sense.
   (user 2026-06-11, reversing the 2026-06-10 "always show" while-paused rule.) */
.rest-timer-overlay.has-log:not(.paused) .rest-timer-quick-picks { display: flex; }
.rest-timer-overlay.has-log:not(.paused) .rest-timer-presets { display: grid; }
/* The log form is hidden while the rest COUNTS; it returns at GO! (paused). */
.rest-timer-overlay.has-log:not(.warmup):not(.paused) .rest-timer-log-inputs,
.rest-timer-overlay.has-log:not(.warmup):not(.paused) .rest-timer-log-actions,
.rest-timer-overlay.has-log:not(.warmup):not(.paused) .rest-timer-log-hold { display: none; }
.rest-timer-log-hint { font-size: 12px; color: var(--text-secondary); text-align: center; max-width: 300px; }
/* The lift/exercise name on the timer log form, so you always know which lift. */
.rest-timer-log-lift { font-size: 16px; font-weight: 700; text-align: center; }
/* Warm-up step card (guide only): big read-only weight × reps + a label. */
.rest-timer-log-warmup-label { font-size: 12px; text-transform: uppercase; letter-spacing: 1px; color: var(--text-secondary); text-align: center; }
.rest-timer-log-warmup-target { font-size: 26px; font-weight: 700; text-align: center; }
/* Warm-up timer (user 2026-06-09): a single focal lift, no small "up next" line.
   PAUSED → show the current step + Done. COUNTING → show the NEXT lift (big) and
   hide Done (you're resting; it auto-advances at GO!). Skip stays available. */
.rest-timer-overlay.warmup.paused .rest-timer-log-next { display: none; }
.rest-timer-overlay.warmup:not(.paused) .rest-timer-log-current,
.rest-timer-overlay.warmup:not(.paused) .rt-log-confirm { display: none; }
/* Working set: the "up next" reps × weight shows while the rest COUNTS; it's
   replaced by the log form once you're doing the set (paused). (user 2026-06-09) */
.rest-timer-overlay.has-log:not(.warmup).paused .rest-timer-log-next { display: none; }

/* Info-style "use dumbbells" note for sub-bar prescribed weights — neutral
   (not a warning colour), just a helpful aside. */
.dumbbell-hint {
  font-size: 12px;
  color: var(--text-secondary);
  background: var(--surface-2);
  border-radius: var(--radius);
  padding: 6px 10px;
}

/* ── Workout date field ────────────────────────────────────────────────────
   The native <input type="date"> renders like a select dropdown on mobile
   WebKit, which reads as a plain dropdown rather than a date field (user
   feedback 2026-06-01). On touch devices, strip that dropdown chrome and
   surface a clear, tinted calendar glyph so it reads as a tappable date field
   that opens the OS picker — matching the desktop look the user already likes.
   Desktop (pointer: fine) keeps its native appearance untouched. */
@media (pointer: coarse) {
  #workout-date-input {
    -webkit-appearance: none;
    appearance: none;
    min-width: 150px;
    text-align: left;
  }
  #workout-date-input::-webkit-date-and-time-value { text-align: left; }
  #workout-date-input::-webkit-calendar-picker-indicator {
    opacity: 1;
    cursor: pointer;
    margin-left: 6px;
    filter: invert(1); /* dark glyph → light, visible on the dark field */
  }
  #workout-date-input::-webkit-inner-spin-button { display: none; }
}

/* Biometric lock overlay — covers the whole app until the user unlocks. Highest
   z-index so nothing shows through. (user 2026-06-13) */
.biometric-lock-overlay {
  position: fixed;
  inset: 0;
  z-index: 9000;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.biometric-lock-card { text-align: center; max-width: 320px; }
.biometric-lock-icon { font-size: 44px; margin-bottom: 8px; }
.biometric-lock-card h2 { margin-bottom: 6px; }
.biometric-lock-card .btn { margin-top: 16px; width: 100%; }

/* Plate calculator (feat_plate_calculator) */
.plate-chip {
  display: inline-block;
  padding: 6px 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-weight: 700;
  font-size: 15px;
  color: var(--text);
}
.plate-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  background: var(--surface);
  border: 1px solid var(--surface-2);
  border-radius: var(--radius-sm);
  font-size: 14px;
  cursor: pointer;
}

/* Body tab — bodyweight + measurements (feat_bodyweight_measurements) */
.body-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 14px;
  margin-bottom: 10px;
}
.body-field > span { color: var(--text-secondary); }
.body-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 12px;
}
/* Each field is a stacked label+input that fills its grid column. Without min-width:0
   the number inputs keep their intrinsic width and the 2-col grid can't shrink → the
   tape-measurements form overflows narrow screens. (fix_body_tape_measurements_width) */
.body-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  margin-bottom: 12px;
}
.body-field input { width: 100%; min-width: 0; }

/* Home relevant quick-links (feat_home_hub_relevant_cards) */
.hub-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
  gap: 8px;
  margin-bottom: 16px;
}
/* Card columns: 1 (single, full-width, stacked) | 2 (default) | 3. (feat_home_columns) */
.hub-cards.columns-1 { grid-template-columns: 1fr; }
.hub-cards.columns-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.hub-cards.columns-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
/* Legacy two-way layout class (superseded by columns-N) — kept harmless. */
.hub-cards.layout-rows { grid-template-columns: 1fr; }

/* Emoji glyph used by a customised centre toolbar tab — sized to match the .tab svg/img. */
.tab-emoji { font-size: 20px; line-height: 24px; height: 24px; opacity: 0.55; transition: opacity 0.2s; }
.tab.active .tab-emoji { opacity: 1; }

/* Home-customiser extra sections (card layout + centre-tab picker). */
.hub-edit-section { margin-top: 16px; }
.hub-edit-section-label { font-size: 13px; font-weight: 700; color: var(--text-secondary); margin-bottom: 8px; }
.hub-seg { display: flex; gap: 0; border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; }
.hub-seg-opt { flex: 1; padding: 10px 12px; background: var(--surface); color: var(--text-secondary); border: 0; font-weight: 600; font-size: 14px; cursor: pointer; }
.hub-seg-opt + .hub-seg-opt { border-left: 1px solid var(--border); }
.hub-seg-opt.active { background: var(--primary); color: #fff; }
.hub-middle-select { width: 100%; padding: 10px 12px; background: var(--surface); color: var(--text); border: 1px solid var(--border); border-radius: var(--radius-sm); font-size: 14px; }
.hub-card {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  padding: 12px 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  text-decoration: none;
  font-weight: 600;
  font-size: 14px;
}
.hub-card span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hub-card:active { background: var(--surface-2); }
/* Customise affordance under the hub cards (feat_home_customisable_cards). Subtle
   text button so it doesn't compete with the cards themselves. The cards grid
   keeps its own margin-bottom; pull the button up to sit just beneath it. */
.hub-customise {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin: -8px 0 16px;
  padding: 4px 2px;
  background: none;
  border: none;
  color: var(--text-secondary);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.hub-customise:active { color: var(--text); }
/* The Customise + View-all affordances sit on one row under the hub cards. */
.hub-actions { display: flex; align-items: center; gap: 16px; }
.hub-actions .hub-customise { text-decoration: none; }
/* Country (+ US state) picker — stacked fields, label above each select. */
.location-picker { display: flex; flex-direction: column; gap: 10px; }
.location-field { display: flex; flex-direction: column; gap: 4px; }
.location-label { font-size: 13px; color: var(--text-secondary); }
.location-picker select { width: 100%; }
/* Hub customiser modal: one reorderable toggle row per card. */
.hub-edit-list { display: flex; flex-direction: column; gap: 6px; }
.hub-edit-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.hub-edit-arrows { display: flex; flex-direction: column; gap: 2px; }
.hub-edit-arrows button {
  background: none;
  border: none;
  color: var(--text-secondary);
  cursor: pointer;
  padding: 0;
  font-size: 14px;
  line-height: 1;
}
.hub-edit-arrows button:disabled { visibility: hidden; }
.hub-edit-label { flex: 1; min-width: 0; font-weight: 600; font-size: 14px; }
.hub-edit-toggle { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-secondary); white-space: nowrap; }
/* Active-program "next session" card on the home hub — accented so it reads as the
   day's primary action. (feat_home_hub_program_route_and_customise) */
.active-program-card {
  border-left: 3px solid var(--primary);
  padding: 14px 16px;
}
.active-program-card:active { background: var(--surface-2); }

/* Weekly recap + streak home card (feat_streaks_weekly_recap) */
.recap-card { padding: 14px 16px; }
.streak-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  /* primary→secondary accent gradient (feat_secondary_accent_colours) */
  background: linear-gradient(135deg, var(--primary), var(--primary-dark));
  color: #fff;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 700;
  white-space: nowrap;
}
.recap-stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
}
.recap-stat { text-align: center; }
.recap-stat-value { font-size: 20px; font-weight: 700; color: var(--text); }
.recap-stat-label { font-size: 11px; color: var(--text-secondary); margin-top: 2px; }
.recap-up { color: var(--success, #10b981); }
.recap-down { color: var(--text-secondary); }

/* Tabbed control area on the rest timer (feat_session_timer_tabs) */
.rest-timer-tabs { width: 100%; max-width: 360px; margin: 4px auto 0; }
.rt-tabbar {
  display: flex;
  gap: 4px;
  background: var(--surface-2);
  border-radius: 999px;
  padding: 3px;
}
.rt-tab {
  flex: 1;
  border: none;
  background: transparent;
  color: var(--text-secondary);
  font-size: 13px;
  font-weight: 600;
  padding: 7px 4px;
  border-radius: 999px;
  cursor: pointer;
}
.rt-tab.active { background: var(--surface); color: var(--text); }
/* Fixed-height panel area + centered content so the tab bar above never shifts
   when you switch tabs (the overlay is vertically centred). (feat_session_timer_tabs) */
.rt-tabpanel {
  padding-top: 10px;
  min-height: 210px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.rt-tabpanel[hidden] { display: none; }
.rt-plates-head { text-align: center; font-size: 13px; color: var(--text-secondary); margin-bottom: 8px; }
.rt-plate-chips { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; }
.rt-plate-chip {
  display: inline-block;
  padding: 6px 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-weight: 700;
  font-size: 15px;
}
.rt-panel-empty, .rt-panel-warn { text-align: center; font-size: 13px; color: var(--text-secondary); margin: 4px 0 0; }
/* How-to tab: exercise name, a brief form cue, and the Info/Coaching demo links. */
.rt-howto-name { font-weight: 700; font-size: 14px; text-align: center; margin-bottom: 6px; }
.rt-howto-cue { font-size: 13px; line-height: 1.5; color: var(--text); margin: 0; text-align: center; }
.rt-howto-links { margin-top: 10px; display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 6px; }
.rt-howto-links-label { font-size: 12px; color: var(--text-secondary); }
.rt-panel-warn { color: var(--warning, #f59e0b); }
.rt-panel-loading { text-align: center; padding: 12px 0; }
.rt-history-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 7px 4px;
  border-bottom: 1px solid var(--border);
  font-size: 14px;
}
.rt-history-row:last-child { border-bottom: none; }
.rt-history-date { color: var(--text-secondary); white-space: nowrap; }
.rt-history-sets { text-align: right; font-weight: 600; }
.rt-history-unit { color: var(--text-secondary); font-weight: 400; font-size: 12px; }
/* "Today" tab — the day's exercises with progress (feat_rest_timer_today_tab). */
.rt-today-list { list-style: none; margin: 0; padding: 0; }
.rt-today-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 4px;
  border-bottom: 1px solid var(--border);
  font-size: 14px;
}
.rt-today-item:last-child { border-bottom: none; }
.rt-today-mark { width: 16px; text-align: center; color: var(--text-secondary); flex: none; }
.rt-today-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rt-today-count { color: var(--text-secondary); font-size: 12px; font-weight: 600; white-space: nowrap; }
.rt-today-item.done .rt-today-mark { color: var(--success, #22c55e); }
.rt-today-item.done .rt-today-name { color: var(--text-secondary); }
.rt-today-item.current { font-weight: 700; }
.rt-today-item.current .rt-today-mark { color: var(--primary); }
.rt-today-item.skipped .rt-today-name { text-decoration: line-through; color: var(--text-secondary); }
/* Read-only superset pairing in the Today list: a shared accent rail down the left
   edge of the two joined rows + a small 'superset' tag on the first. (owner 2026-07-04) */
.rt-today-item.ss { border-left: 3px solid var(--primary); padding-left: 8px; }
.rt-today-item.ss-first { border-top-left-radius: var(--radius-sm, 6px); }
.rt-today-item.ss-last { border-bottom-left-radius: var(--radius-sm, 6px); }
.rt-today-ss-tag {
  margin-left: 8px; font-size: 10px; font-weight: 700; letter-spacing: 0.06em;
  color: var(--primary); vertical-align: middle;
}
.rt-plates-bar { text-align: center; font-size: 12px; color: var(--text-secondary); margin-bottom: 6px; }
.rt-plates-entry { display: flex; flex-direction: column; align-items: center; gap: 6px; margin-bottom: 6px; }
/* Per-exercise loading editor — plate toggle chips. (feat_per_exercise_loading_editor) */
.le-plates { display: flex; flex-wrap: wrap; gap: 6px; }
.le-plate { border: 1px solid var(--border); background: var(--surface-2); color: var(--text); border-radius: 999px; padding: 6px 12px; font-size: 13px; cursor: pointer; }
.le-plate.on { background: var(--primary); color: #fff; border-color: var(--primary); }
.rt-plates-entry label { font-size: 13px; color: var(--text-secondary); }
.rt-plates-entry input { max-width: 130px; text-align: center; }

/* Coloured-disc plate picker — a 2-column grid (coloured disc · weight · "have it"
   checkbox · count) shared by Settings → Equipment and the timer's plate editor.
   A blank count = unlimited (∞ placeholder). (feat_plate_picker_colours) */
/* Two explicit columns: heavy plates (5kg+/10lb+) left, light change plates right. */
.plate-grid { display: flex; gap: 16px; align-items: flex-start; }
.plate-col { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.plate-cell { display: flex; align-items: center; gap: 8px; padding: 3px 0; }
.plate-pick { display: flex; align-items: center; gap: 8px; cursor: pointer; flex: 1; min-width: 0; }
/* Push the ownership checkbox to the right edge of the (flex:1) label so the
   checkboxes line up in a column adjacent to the count text box, rather than
   floating just after variable-width weight labels. (fix_plate_picker_checkbox_align) */
.plate-pick .plate-own { margin-left: auto; }
/* The competition-colour disc, drawn as a ring with a transparent centre hole (like
   a real plate). White/pale plates keep a border so their edge reads on any theme.
   The hole is punched with a radial mask so it's genuinely see-through. */
.plate-disc {
  width: 20px; height: 20px; flex: none; border-radius: 50%; border: 1px solid var(--border);
  /* Smaller centre hole — about half the previous radius (owner 2026-07-01). */
  -webkit-mask: radial-gradient(circle at 50% 50%, transparent 0 16%, #000 18%);
          mask: radial-gradient(circle at 50% 50%, transparent 0 16%, #000 18%);
}
/* Max loadable weight line under the plate picker — reserve height so it never shifts the
   modal as it flips between a total and ∞. (owner 2026-07-01) */
.le-max { min-height: 1.4em; margin-top: 8px; font-weight: 600; font-size: 14px; text-align: center; }
/* The ∞ glyph reads clearer a size up — in the max line + as the unlimited-count placeholder. (owner 2026-07-01) */
.rt-inf { font-size: 1.6em; line-height: 1; vertical-align: -0.12em; }
.plate-wt { font-weight: 600; }
.plate-cell .plate-count { width: 64px; flex: none; text-align: right; padding: 6px 8px; }
.plate-cell .plate-count::placeholder { font-size: 1.5em; opacity: 0.7; }
.plate-cell:not(.on) .plate-pick { opacity: 0.55; }

/* Bands inventory rows (Settings → Equipment): the label takes its own line and
   the count/tension fields wrap below, so the three number inputs never overflow
   the card on a narrow phone. (feat_settings_equipment_polish) */
.resist-band-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; padding: 6px 0; }
.resist-band-row .rb-label { flex: 1 1 100%; font-weight: 500; }
.resist-band-row .rb-fields { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.resist-band-row .rb-fields input { width: 56px; text-align: right; }
/* The compact numeric inputs in the inline bands/chains panel must escape the
   `.settings-select { min-width: 180px }` default, or they blow out the card
   (the cause of the bands overflow). Their own width (56px / inline) then wins. */
.resist-config .settings-select { min-width: 0; }

/* Custom program builder (feat_custom_program_builder) */
.cb-mini { display: flex; flex-direction: column; gap: 2px; font-size: 12px; }
.cb-mini > span { color: var(--text-secondary); }
.cb-mini input { width: 100%; }

/* Analytics consent banner (feat_session_replay_rrweb_or_posthog). A bottom
   sheet shown once until the user opts in/out of PostHog product analytics.
   Sits above the tab bar (z 200 > tab-bar's 100). */
#analytics-consent {
  position: fixed;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  width: 100%;
  max-width: 480px;
  z-index: 200;
  background: var(--surface);
  border-top: 1px solid var(--border);
  box-shadow: 0 -6px 20px rgba(0, 0, 0, 0.25);
  padding-bottom: env(safe-area-inset-bottom);
  animation: analytics-consent-in 0.18s ease-out;
}
@keyframes analytics-consent-in { from { transform: translate(-50%, 100%); } to { transform: translate(-50%, 0); } }
.analytics-consent-inner { padding: 14px 16px; display: flex; flex-direction: column; gap: 12px; }
.analytics-consent-text { margin: 0; font-size: 13px; line-height: 1.4; color: var(--text-secondary); }
.analytics-consent-text a { color: var(--link); white-space: nowrap; }
.analytics-consent-actions { display: flex; gap: 8px; justify-content: flex-end; }
.analytics-consent-actions .btn { flex: 0 0 auto; min-width: 96px; }

/* ── Progression scripting authoring (feat_progression_scripting_authoring_ui) ── */
.ps-intro, .ps-library .ps-intro { color: var(--text-secondary); margin: 4px 0 16px; }
.ps-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; }
.ps-actions .btn { flex: 1 1 auto; }
.ps-section { font-size: 15px; margin: 22px 0 8px; }
.ps-script-card { display: block; text-decoration: none; color: inherit; margin-top: 8px; }
.ps-script-head { display: flex; align-items: center; gap: 8px; justify-content: space-between; }
.ps-script-sub { color: var(--text-secondary); font-size: 13px; margin-top: 4px; }
.ps-hook-tag { display: inline-block; font-family: ui-monospace, monospace; font-size: 11px; background: var(--surface-2); border-radius: var(--radius-sm); padding: 1px 6px; }
.ps-origin { font-size: 11px; color: var(--text-secondary); }
.ps-status { font-size: 11px; border-radius: var(--radius-sm); padding: 1px 7px; text-transform: capitalize; }
.ps-status-pending { background: var(--warning); color: #2a1d00; }
.ps-status-approved { background: var(--success); color: #032a12; }
.ps-status-rejected { background: var(--danger); color: #fff; }
.ps-refs { display: grid; gap: 8px; }
.ps-ref-card { display: flex; flex-direction: column; gap: 3px; text-decoration: none; color: inherit; }
.ps-ref-card span { color: var(--text-secondary); font-size: 13px; }
.ps-ref-card .ps-hook-tag { align-self: flex-start; }

/* editor widget */
.ps-editor { display: flex; flex-direction: column; gap: 10px; }
.ps-toolbar { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.ps-toolbar .btn { flex: 0 0 auto; }
.ps-fork-wrap { position: relative; }
.ps-fork-menu { position: absolute; z-index: 30; top: 100%; left: 0; margin-top: 4px; width: min(320px, 86vw);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: 0 8px 28px rgba(0,0,0,.28); overflow: hidden; }
.ps-fork-item { display: flex; flex-direction: column; gap: 2px; width: 100%; text-align: left; padding: 10px 12px; background: none; border: none; border-bottom: 1px solid var(--border); cursor: pointer; color: inherit; }
.ps-fork-item:last-child { border-bottom: none; }
.ps-fork-item:hover { background: var(--surface-2); }
.ps-fork-item span { font-size: 12px; color: var(--text-secondary); }

.ps-meta { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.ps-name-input { flex: 2 1 180px; }
.ps-desc-input { flex: 3 1 220px; }
.ps-hook-label { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-secondary); }
.ps-hook-select { padding: 6px; }
.ps-hooks-detected { flex-basis: 100%; font-size: 12px; color: var(--primary); }

.ps-code-wrap { position: relative; display: flex; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; background: var(--bg); }
.ps-gutter { margin: 0; padding: 10px 6px 10px 10px; text-align: right; color: var(--text-secondary); opacity: .6;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; line-height: 1.5; user-select: none;
  overflow: hidden; min-width: 2.4em; background: var(--surface-2); white-space: pre; }
.ps-code { flex: 1; border: none; resize: vertical; min-height: 320px; padding: 10px; margin: 0; background: var(--bg); color: var(--text);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; line-height: 1.5; tab-size: 2; white-space: pre; overflow: auto; }
.ps-code:focus { outline: none; }

.ps-tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--border); margin-top: 2px; }
.ps-tab { background: none; border: none; padding: 8px 12px; cursor: pointer; color: var(--text-secondary); font-weight: 600; font-size: 13px; border-bottom: 2px solid transparent; }
.ps-tab.active { color: var(--text); border-bottom-color: var(--primary); }
.ps-badge { display: inline-block; min-width: 18px; text-align: center; background: var(--danger); color: #fff; border-radius: 9px; font-size: 11px; padding: 0 5px; }
.ps-panels { min-height: 120px; }
.ps-panel { padding: 10px 2px; }
.ps-contract, .ps-sample, .ps-json { margin: 0; padding: 10px; background: var(--surface-2); border-radius: var(--radius);
  font-family: ui-monospace, monospace; font-size: 12px; line-height: 1.45; overflow: auto; max-height: 360px; white-space: pre; }

.ps-result { display: flex; flex-direction: column; gap: 6px; }
.ps-result-scope { font-size: 11px; color: var(--text-secondary); font-family: ui-monospace, monospace; }
.ps-result-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: baseline; padding: 8px 10px; background: var(--surface-2); border-radius: var(--radius); }
.ps-result-key { font-weight: 700; }
.ps-slot { font-size: 13px; }
.ps-slot strong { color: var(--success); }
.ps-hold { color: var(--text-secondary); }
.ps-ok { color: var(--success); font-weight: 600; }
.ps-warn { color: var(--warning); font-weight: 600; }
.ps-err { color: var(--danger); }
.ps-err code { background: var(--surface-2); padding: 1px 4px; border-radius: 4px; }

.ps-problem { display: flex; flex-direction: column; gap: 2px; width: 100%; text-align: left; padding: 8px 10px; margin-bottom: 6px;
  background: var(--surface-2); border: 1px solid var(--border); border-left: 3px solid var(--danger); border-radius: var(--radius); cursor: pointer; color: inherit; }
.ps-problem-loc { font-size: 11px; color: var(--text-secondary); }
.ps-problem-tok { font-family: ui-monospace, monospace; font-weight: 700; color: var(--danger); }
.ps-problem-msg { font-size: 13px; }
.ps-import-input { width: 100%; margin: 8px 0; }

/* lessons */
.ps-lesson-card { border: 1px solid var(--border); border-radius: var(--radius); padding: 14px; margin-bottom: 12px; background: var(--surface); }
.ps-lesson-top { display: flex; gap: 8px; align-items: center; margin-bottom: 6px; }
.ps-lesson-step { font-size: 12px; color: var(--text-secondary); font-weight: 700; }
.ps-lesson-feature { font-size: 11px; background: var(--surface-2); border-radius: var(--radius-sm); padding: 2px 8px; }
.ps-lesson-body { color: var(--text); line-height: 1.55; }
.ps-lesson-body code { background: var(--surface-2); padding: 1px 5px; border-radius: 4px; font-family: ui-monospace, monospace; font-size: 12px; }
.ps-lesson-body ul { padding-left: 18px; }
.ps-lesson-try { margin-top: 10px; padding: 10px 12px; background: var(--surface-2); border-radius: var(--radius); font-size: 14px; }
.ps-lesson-nav { display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap; }
.ps-lesson-nav .btn { flex: 1 1 auto; }

/* roomier editor on wider screens */
@media (min-width: 760px) {
  .ps-code { min-height: 420px; }
}

/* ── Curated white-label fonts (feat_tenant_branding_fonts) ───────────────────
   Self-hosted latin-subset woff2 (no CDN). Lazy: only fetched when a tenant
   selects one (sets --font to the family); non-tenant users never load them. */
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: swap; src: url('../assets/fonts/inter-400.woff2') format('woff2'); }
/* Forge assistance editor: the 'superset with next' toggle has nothing to pair with on the
   LAST row, so hide it there. (feat_forge_assistance_superset_pairing) */
.assist-rows > .assist-row:last-child .assist-ss-wrap { display: none; }

/* Ad-hoc superset connector between two adjacent assistance cards
   (feat_superset_adhoc_assistance): OFFER (not joined) = a centred "⮂ Superset" pill;
   JOINED = a small 'superset' tag (+ ✕ to unjoin on user pairs). No brace. */
.superset-connector { display: flex; align-items: center; justify-content: center; gap: 8px; margin: 1px 0; }
.superset-toggle { font-size: 12px; padding: 4px 10px; }
.superset-badge { font-size: 12px; font-weight: 600; }
/* The un-joined OFFER (log view) — Superset AND Compound/Tri/Giant (.round-extend) — is ONE consistent
   rounded accent-outlined pill, transparent interior, no faint line/fill behind. (fix_grouped_set_offer_pill_style;
   was: superset = borderless faint text, extend = grey btn-outline — inconsistent.) */
.superset-connector:not(.is-joined) .superset-toggle,
.superset-connector .round-extend {
  opacity: 1;
  color: var(--primary);
  border: 1px solid var(--primary);
  border-radius: 999px;
  background: transparent;
  padding: 3px 12px; /* symmetric → label centred in the fixed line box (fix_offer_pill_vcenter) */
  font-size: 12px;
  width: auto;
  /* Fixed line box so ⮂ (fallback font) and '+' pills are identical height. (fix_offer_pill_lineheight) */
  line-height: 15px;
}
.superset-tag { font-size: 11px; font-weight: 700; letter-spacing: 0.06em; color: var(--primary); }
.superset-tag-btn { background: none; border: none; padding: 2px 4px; cursor: pointer; color: var(--primary); display: inline-flex; align-items: center; gap: 5px; }
/* All superset controls are bare <button>s — force the app font so they never fall back
   to the UA button font (the "two different fonts" the pill showed). (owner 2026-07-03) */
.superset-rail, .superset-tag-btn, .day-ss-btn, .superset-toggle, .superset-tag { font-family: inherit; }

/* JOINED pair, LOG VIEW (#assistance-list): the two cards are wrapped in a
   .superset-group so ONE accent rail spans both — a single 3px left border with the
   word 'superset' set vertically along it. No per-card edge, no brace. The rail is the
   unjoin control for an ad-hoc pair; a program-authored pair (.is-server) is read-only.
   (owner 2026-07-03: drop the { and the per-card edge — one bar spanning both, label
   vertical along it.) */
.superset-group {
  position: relative;
  border-left: 3px solid var(--primary);
  /* Curve the top & bottom ends inward, matching the rounded card edge the accent
     border used to sit on. (owner 2026-07-03) */
  border-top-left-radius: var(--radius);
  border-bottom-left-radius: var(--radius);
  padding-left: 26px;
  margin: 10px 0;
}
.superset-group-cards { display: flex; flex-direction: column; }
/* The last card's 12px bottom margin sits INSIDE the group, so the accent rail /
   border-left overshoots past the card. Drop it so the rail ends at the card edge.
   (owner 2026-07-04) */
.superset-group-cards > :last-child { margin-bottom: 0; }
.superset-rail {
  position: absolute; left: 0; top: 0; bottom: 0; width: 26px;
  display: flex; align-items: center; justify-content: center;
  writing-mode: vertical-rl; transform: rotate(180deg);
  background: none; border: none; padding: 0; cursor: pointer;
  font-size: 11px; font-weight: 700; letter-spacing: 0.08em;
  color: var(--primary);
}
.superset-group.is-server .superset-rail { cursor: default; }

/* PROGRAM LISTING (.session-preview): a JOINED pair renders as a vertical rail beside
   the two combined rows (via .superset-group, shared with the log view); the OFFER
   (not joined) sits on the divider line, pulled all the way to the RIGHT edge of the row.
   (owner 2026-07-04: vertical line beside the combined; the un-joined offer pill sits far right.)
   The connector is zero-height; its button is absolutely placed onto the boundary. */
.session-preview .superset-connector { position: relative; height: 0; margin: 0; overflow: visible; display: block; }
.session-preview .superset-connector > * {
  position: absolute; right: 0; top: 0; transform: translateY(-50%);
  background: var(--surface); padding-left: 8px;
}
/* The offer pill sits ON the row-boundary divider, pulled far right. Keep the divider line between
   exercises, but give the pill an OPAQUE background so it MASKS the line just behind itself — the line
   runs across the row and is cleanly interrupted where the pill sits, rather than removed or running
   through it. (owner 2026-07-08: "I want the line, just not where it runs behind the new buttons".) */
.session-preview .superset-connector .superset-toggle,
.session-preview .superset-connector .round-extend {
  font-size: 11px; padding: 2px 8px; background: var(--surface); line-height: 14px; /* symmetric → centred in fixed line box (fix_offer_pill_vcenter) */
}
/* Joined pair rail, sized down for the compact listing rows. */
.session-preview .superset-group { margin: 4px 0; padding-left: 22px; }
.session-preview .superset-rail { width: 22px; font-size: 10px; letter-spacing: 0.06em; }

@font-face { font-family: 'Inter'; font-style: normal; font-weight: 600; font-display: swap; src: url('../assets/fonts/inter-600.woff2') format('woff2'); }
@font-face { font-family: 'Roboto'; font-style: normal; font-weight: 400; font-display: swap; src: url('../assets/fonts/roboto-400.woff2') format('woff2'); }
@font-face { font-family: 'Roboto'; font-style: normal; font-weight: 600; font-display: swap; src: url('../assets/fonts/roboto-600.woff2') format('woff2'); }
@font-face { font-family: 'Poppins'; font-style: normal; font-weight: 400; font-display: swap; src: url('../assets/fonts/poppins-400.woff2') format('woff2'); }
@font-face { font-family: 'Poppins'; font-style: normal; font-weight: 600; font-display: swap; src: url('../assets/fonts/poppins-600.woff2') format('woff2'); }
@font-face { font-family: 'Lato'; font-style: normal; font-weight: 400; font-display: swap; src: url('../assets/fonts/lato-400.woff2') format('woff2'); }
@font-face { font-family: 'Lato'; font-style: normal; font-weight: 600; font-display: swap; src: url('../assets/fonts/lato-600.woff2') format('woff2'); }
@font-face { font-family: 'Montserrat'; font-style: normal; font-weight: 400; font-display: swap; src: url('../assets/fonts/montserrat-400.woff2') format('woff2'); }
@font-face { font-family: 'Montserrat'; font-style: normal; font-weight: 600; font-display: swap; src: url('../assets/fonts/montserrat-600.woff2') format('woff2'); }

/* Full-screen boot blocker (feat_client_bootstrap_handshake): shown when the API reports
   this app version is unsupported (update_required) or it's in maintenance. */
.app-blocker { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; padding: 24px; background: var(--bg); z-index: 9999; }
.app-blocker-card { max-width: 360px; text-align: center; }
.app-blocker-mark { font-size: 44px; margin-bottom: 12px; }
.app-blocker-card h1 { font-size: 22px; margin: 0 0 10px; color: var(--text); }
.app-blocker-card p { color: var(--text-secondary); line-height: 1.5; margin: 0; }
