
    /* ===== 1. Design tokens (light + dark themes) ===== */
    :root {
      /* Design tokens: warm-paper light theme + true-black dark theme. */
      --bg: #faf4ed;
      --surface: #fffaf3;
      --surface-2: #f2e9e1;
      --surface-raised: #fffdf8;
      --ink: #1c1c1e;
      --ink-strong: #000000;
      --muted: #6c6c70;
      --line: #dfdad9;
      --line-strong: #cecacd;
      --switch-track: #a9a2a8;
      --accent: #2e7d32;
      --accent-hover: #256429;
      --accent-soft: #ddefdc;
      --pine: #2e7d32;
      --pine-soft: #ddefdc;
      --rose: #d7827e;
      --rose-soft: #f5dfdc;
      --gold: #ea9d34;
      --gold-soft: #fae9cc;
      --foam: #5da464;
      --danger: #d92d20;
      --danger-soft: #fbe3e1;
      /* #84 (v1.007): swipe-to-delete rail red, harmonized with the theme
         accent (danger 78% + accent 22%, mixed in sRGB). */
      --swipe-rail-bg: #b33f24;
      --on-accent: #ffffff;
      /* #555: light-theme warning darkened to #9a5515 — the old #b86b20 was
         3.0:1 on the light background (WCAG AA needs 4.5:1 for text). */
      --warning: #9a5515;
      /* B27 (#99): swipe-to-delete rail width — the delete-action width and
         the open-state translation must stay in sync. */
      --swipe-rail: 72px;
      --focus-ring: rgba(46,125,50,.22);
      --shadow-color: rgba(28,28,30,.10);
      --backdrop: rgba(0,0,0,.32);
      --theme-color: #faf4ed;
      --nav-bg: rgba(255,253,248,.82);
      --heat-0: #e7e3e1;
      --heat-1: #ddefdc;
      --heat-2: #b7dfb9;
      --heat-3: #86c98b;
      --heat-4: #5da464;
      --heat-5: #2e7d32;
      /* Phase 2 (v1.901) design tokens — lift verbatim from the
         visual-refresh mockup's "Design tokens" section: one shape per
         family, families complementary. Squared for structure and
         actions (cards, buttons, icon buttons, inputs); pill reserved for
         switch tracks and progress bars. */
      --radius-xs: 4px;     /* micro marks: heat swatches, chart dots */
      --radius-sm: 8px;     /* badges, tags, numeric keypad squares */
      --radius-md: 12px;    /* buttons (primary + secondary), icon buttons,
                               inputs, rows */
      --radius-lg: 16px;    /* cards, dialogs, sheets */
 /* switch tracks and progress bars only */
      /* Motion (#620, v1.908): slight, research-backed spec — page/view
         changes fade at 120ms, sheet/dialog entrances settle at 180ms,
         exits leave at 120ms. Easings are the standard Material curves:
         standard for in-place changes, decelerate for entrances,
         accelerate for exits. Every animated surface uses these tokens;
         no surface invents its own duration or easing. */
      --motion-page: 120ms;
      --motion-enter: 180ms;
      --motion-exit: 120ms;
      --motion-micro: 120ms; /* hover/press/chevron/switch micro-feedback */
      --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
      --ease-decelerate: cubic-bezier(0, 0, 0.2, 1);
      --ease-accelerate: cubic-bezier(0.4, 0, 1, 1);
      /* spacing */
  --space-2: 8px;  --space-3: 12px;
      --space-4: 16px; --space-5: 24px;      /* control sizes — complementary weights */
      --icon-btn: 40px;   /* every icon button, one box */
      --btn-h: 44px;      /* standard buttons & form actions */
      --btn-h-lg: 52px;   /* primary CTAs only: flat, no shadow */
      --btn-h-sm: 40px;   /* compact buttons, selector controls */
      --input-h: 44px;    /* inputs, selects, search */
      --input-h-sm: 44px; /* dense inputs: set rows stay thumb-sized */
      --card-pad: 16px;   /* every card */
      /* shadows — borders carry structure; shadows lift dialogs only */
      --shadow: 0 2px 6px rgba(60,48,20,.05), 0 14px 32px rgba(60,48,20,.09);
      --shadow-soft: 0 1px 2px rgba(60,48,20,.04), 0 4px 14px rgba(60,48,20,.04);
      --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
      /* Select chevron (#9): custom arrow so it's visible in every theme. */
      --chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%236e6e78' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    }
    [data-theme="macchiato"], [data-theme="mocha"] { /* #99 B20: dark branch deleted — unreachable */
      --chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23b6b6c0' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    }
    /* ===== Alt palettes: Catppuccin (verified: catppuccin/palette) + Rosé Pine Dawn (rosepinetheme.com) ===== */
    [data-theme="rosepine"] {
      color-scheme: light;
      --bg: #faf4ed;
      --surface: #fffaf3;
      --surface-2: #f2e9e1;
      --surface-raised: #fffaf3;
      --ink: #575279;
      --ink-strong: #393552;
      --muted: #9893a5;
      --line: #dfdad9;
      --line-strong: #cecacd;
      --switch-track: #b9b0bd;
      --accent: #b4637a;
      --accent-hover: #a2546a;
      --accent-soft: rgba(180,99,122,.12);
      --pine: #286983;
      --pine-soft: rgba(40,105,131,.12);
      --rose: #d7827e;
      --rose-soft: rgba(215,130,126,.12);
      --gold: #ea9d34;
      --gold-soft: rgba(234,157,52,.14);
      --foam: #56949f;
      --danger: #b4637a;
      --danger-soft: rgba(180,99,122,.10);
      /* #84 (v1.007): rosepine danger already equals the accent — the rail
         is that rose, harmonized by construction. */
      --swipe-rail-bg: #b4637a;
      --on-accent: #fffaf3;
      --warning: #ea9d34;
      --focus-ring: rgba(180,99,122,.25);
      --shadow-color: rgba(87,82,121,.12);
      --backdrop: rgba(87,82,121,.32);
      --theme-color: #faf4ed;
      --nav-bg: rgba(250,244,237,.82);
      --heat-0: #dfdad9;
      --heat-1: #f3e3e6;
      --heat-2: #e9c6cd;
      --heat-3: #d89cab;
      --heat-4: #c47384;
      --heat-5: #b4637a;
    }
    [data-theme="macchiato"] {
      color-scheme: dark;
      --bg: #24273a;
      --surface: #1e2030;
      --surface-2: #363a4f;
      --surface-raised: #1e2030;
      --ink: #cad3f5;
      --ink-strong: #ffffff;
      --muted: #a5adcb;
      --line: #363a4f;
      --line-strong: #494d64;
      --switch-track: rgba(255,255,255,.30);
      /* #220 (user 2026-09-22): the peach accent and brown/orange heat ramp
         felt jarring on the dark background — reworked to the app's green
         (Catppuccin macchiato green), cohesive with the light theme. */
      --accent: #a6da95;
      --accent-hover: #b9e5ac;
      --accent-soft: rgba(166,218,149,.14);
      --pine: #8bd5ca;
      --pine-soft: rgba(139,213,202,.14);
      --rose: #f5bde6;
      --rose-soft: rgba(245,189,230,.14);
      --gold: #eed49f;
      --gold-soft: rgba(238,212,159,.14);
      --foam: #91d7e3;
      --danger: #ed8796;
      --danger-soft: rgba(237,135,150,.14);
      /* #84 (v1.007): swipe rail — macchiato danger warmed 22% toward the
         green accent. */
      --swipe-rail-bg: #dd9996;
      --on-accent: #181926;
      --warning: #eed49f;
      --focus-ring: rgba(166,218,149,.30);
      --shadow-color: rgba(0,0,0,.35);
      --backdrop: rgba(17,17,27,.60);
      --theme-color: #24273a;
      --nav-bg: rgba(36,39,58,.82);
      --heat-0: #363a4f;
      --heat-1: #2e3d30;
      --heat-2: #3c573d;
      --heat-3: #527a4e;
      --heat-4: #77a868;
      --heat-5: #a6da95;
    }
    [data-theme="mocha"] {
      color-scheme: dark;
      --bg: #1e1e2e;
      --surface: #181825;
      --surface-2: #313244;
      --surface-raised: #181825;
      --ink: #cdd6f4;
      --ink-strong: #ffffff;
      --muted: #a6adc8;
      --line: #313244;
      --line-strong: #45475a;
      --switch-track: rgba(255,255,255,.30);
      --accent: #a6e3a1;
      --accent-hover: #b9ecb4;
      --accent-soft: rgba(166,227,161,.14);
      --pine: #94e2d5;
      --pine-soft: rgba(148,226,213,.14);
      --rose: #f5c2e7;
      --rose-soft: rgba(245,194,231,.14);
      --gold: #f9e2af;
      --gold-soft: rgba(249,226,175,.14);
      --foam: #89dceb;
      --danger: #f38ba8;
      --danger-soft: rgba(243,139,168,.14);
      /* #84 (v1.007): swipe rail — mocha danger warmed 22% toward the
         green accent. */
      --swipe-rail-bg: #e29ea6;
      --on-accent: #11111b;
      --warning: #f9e2af;
      --focus-ring: rgba(166,227,161,.30);
      --shadow-color: rgba(0,0,0,.35);
      --backdrop: rgba(17,17,27,.60);
      --theme-color: #1e1e2e;
      --nav-bg: rgba(30,30,46,.82);
      --heat-0: #313244;
      --heat-1: #2c3d2c;
      --heat-2: #3f563d;
      --heat-3: #5a7d54;
      --heat-4: #7fb877;
      --heat-5: #a6e3a1;
    }
    * { box-sizing: border-box; }
    /* ===== 2. Base elements & resets ===== */
    html { font-optical-sizing: auto; --bottom-nav-clearance: calc(82px + env(safe-area-inset-bottom)); --top-bar-clearance: calc(env(safe-area-inset-top) + 66px); background: var(--bg); color: var(--ink); scroll-padding-bottom: var(--bottom-nav-clearance); scroll-padding-top: var(--top-bar-clearance); } /* #162: focused/scrolled-to content clears the fixed top bar, mirroring the bottom-nav padding. */
    /* Native-app feel (2026-09-11): on touch devices only (.is-touch, set
       by JS via pointer:coarse) — no pinch-zoom, no visible scrollbars,
       and no horizontal page panning (pan-y only: the page must never drift
       sideways; set-row swipe is JS-driven with its own touch-action).
       Desktop (fine pointer) is completely untouched. */
    .is-touch { touch-action: pan-y; scrollbar-width: none; }
    /* #1: reorder-exercises modal (2026-09-11) — up/down arrows per row. */
    .reorder-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 20px; }
    .reorder-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); padding: 6px 6px 6px 14px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface-2); }
    .reorder-name { font-weight: 700; font-size: 14px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
    .reorder-superset-tag { font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: .08em; color: var(--accent); border: 1px solid var(--accent); border-radius: var(--radius-sm); padding: 2px 8px; }
    /* #448: the reorder row renders one superset block — the tag plus the
       member names on one row. */
    .reorder-members { font-weight: 600; font-size: 12px; color: var(--muted); }
    .reorder-arrows { display: flex; gap: 4px; flex-shrink: 0; }
    .reorder-arrow { width: var(--icon-btn); height: var(--icon-btn); display: inline-flex; align-items: center; justify-content: center; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); cursor: pointer; }
    .reorder-arrow:disabled { opacity: .3; cursor: default; }
    .reorder-arrow:not(:disabled):active { background: var(--surface-raised); }
    /* Always-on checkboxes in the reorder dialog (user 2026-09-22): each
       block row carries a checkbox on the left plus the reorder arrows. */
    .dialog-head-actions { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
    .reorder-row .reorder-name { flex: 1; }
    .reorder-check { flex: none; background: none; border: 0; padding: 8px 4px 8px 0; margin: 0; cursor: pointer; }
    .select-check { flex: none; width: 26px; height: 26px; border-radius: 50%; border: 2px solid var(--line-strong); background: var(--surface); display: grid; place-items: center; color: transparent; }
    .select-check svg { width: 14px; height: 14px; display: block; }
    .select-check.on { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
    .reorder-select-foot { display: flex; flex-direction: column; gap: 2px; width: 100%; }
    .reorder-select-actions { display: flex; gap: 8px; }
    .reorder-select-actions .primary-button { flex: 1; }
    .reorder-confirm-body { margin: 0 0 6px; color: var(--muted); font-size: 14px; line-height: 1.6; }
    .reorder-confirm-body b { color: var(--ink); }
    .is-touch::-webkit-scrollbar, .is-touch body::-webkit-scrollbar { display: none; }
    .is-touch body { scrollbar-width: none; }
    html, body { overflow-x: hidden; }
    body { margin: 0; min-height: 100vh; font-family: var(--font-sans); background: var(--bg); scroll-padding-bottom: var(--bottom-nav-clearance); }
    button, input, textarea, select { font: inherit; }
    button { color: inherit; }
    a { color: inherit; }
    /* ===== 3. App shell, status scrim & page container ===== */
    /* .app is a size container: on desktop the column is 393px, so all
       width breakpoints below key off the column (container queries), not the
       viewport — the desktop app behaves exactly like the iPhone viewport
       (user 2026-09-10: desktop is just the phone, centered, for testing). */
    .app { min-height: 100vh; padding-bottom: env(safe-area-inset-bottom); container-type: inline-size; }
    /* Opaque backdrop behind the iOS status bar / Dynamic Island so scrolled content
       slides underneath it instead of colliding with the clock. Zero-height elsewhere. */
    /* Q12: the scrim intentionally paints ABOVE the top bar (z-index 40 > 30)
       to tint the iOS status area; it stays safe only because of
       pointer-events: none — never make it interactive. */
    .status-scrim { position: fixed; top: 0; left: 0; right: 0; height: env(safe-area-inset-top); background: var(--bg); z-index: 40; pointer-events: none; }
    .shell { width: min(1120px, 100%); margin: 0 auto; padding: var(--top-bar-clearance) 24px 112px; }
    /* Every view's first block starts at exactly the same offset below the top bar,
       so switching tabs never shifts content vertically. */
    .shell > * > :first-child { margin-top: 0; }
    .add-exercise { min-height: var(--btn-h); padding: 0 18px; border: 0; border-radius: var(--radius-md); background: var(--accent); color: var(--surface-raised); font-weight: 750; cursor: pointer; white-space: nowrap; }
    .add-exercise:hover { background: var(--accent-hover); }
    /* ===== 4. Typography ===== */
    h1, h2, h3, p { margin-top: 0; }
    h1 { margin-bottom: 5px; font: 800 clamp(32px, 5vw, 44px)/1.05 var(--font-sans); letter-spacing: -.03em; }
    .lede { margin: 0; color: var(--muted); font-size: 15px; }
    /* ===== 5. Search & filter controls ===== */
    .controls { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px; margin-bottom: 16px; }
    .search-wrap { position: relative; }
    .search-icon { position: absolute; left: 15px; top: 50%; transform: translateY(-50%); width: 18px; height: 18px; color: var(--muted); pointer-events: none; }
    .search { width: 100%; height: var(--input-h); padding: 0 44px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); outline: none; transition: border-color var(--motion-micro) var(--ease-standard), box-shadow var(--motion-micro) var(--ease-standard); }
    /* C2 (pixel-peeper PP2): the custom .clear-search button is the only
       clear control — suppress the native search cancel button so the two
       never render side by side. #667: the picker search carries the same
       custom button, so its native cancel is suppressed too. */
    .search::-webkit-search-cancel-button, .picker-search::-webkit-search-cancel-button { display: none; }
    .search:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--focus-ring); }
    /* #667 (user 2026-10-18): the clear button rendered low/off-center with a
       small washed-out glyph (top:4px inside a 44px field, no font-size so
       the × fell back to the tiny UA button font). Centered on the field
       like .search-icon (top:50% + translateY), glyph sized/weighted like
       the app's other × icon button (.dialog-close: 22px, line-height 1,
       muted). 44px tap target; the inputs reserve the space with permanent
       right padding, so showing/hiding never shifts layout. */
    .clear-search { position: absolute; right: 0; top: 50%; transform: translateY(-50%); width: 44px; height: 44px; border: 0; border-radius: 50%; background: transparent; color: var(--muted); cursor: pointer; display: none; font-family: inherit; font-size: 22px; line-height: 1; }
    .clear-search.visible { display: grid; place-items: center; }
    .filter-button { min-width: 0; height: var(--input-h); min-height: var(--input-h); padding: 0 12px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 6px; }
    .filter-button[aria-expanded="true"] { border-color: var(--accent); background: var(--accent-soft); }
    .filter-panel { display: none; grid-template-columns: minmax(0, 1.6fr) minmax(220px, .7fr); gap: 18px; margin-bottom: 18px; padding: var(--card-pad); border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface); }
    .filter-panel.open { display: grid; }
    /* #674 (user 2026-10-18): one prominent Clear-all for the whole panel —
       a real secondary button in the panel header row, shown whenever any
       filter is active. It replaces the tiny "Clear" text link that sat
       beside MUSCLES and (in the library) reset only the muscle picks. */
    .filter-panel-head { grid-column: 1 / -1; display: flex; align-items: center; justify-content: space-between; gap: 12px; }
    .filter-clear-all { min-height: var(--btn-h-sm); padding: 0 14px; font-size: 13px; }
    .filter-clear-all[hidden] { display: none; }
    .filter-label-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 8px; }
    /* #238 sub-issue (user 2026-09-12): the PROGRAM row's chip sat too close
       to the muscle list above it. */
    .muscle-options + .filter-label-row { margin-top: 14px; }
    .filter-group label, .filter-label { display: block; color: var(--muted); font-size: 12px; font-weight: 700; }
    .filter-clear { padding: 2px 0; border: 0; background: transparent; color: var(--accent); font-size: 12px; cursor: pointer; }
    .muscle-options { display: flex; flex-wrap: wrap; gap: 7px; }
    /* #670 (user 2026-10-18): chips keep IDENTICAL geometry in both states.
       The #607 selected state bolds the label (font-weight 800), and bold
       text is wider — chips grew on selection and the grid reflowed. The
       label span reserves the bold footprint in both states (an invisible
       800-weight ::after carrying the same text, stacked under the visible
       label at height 0), and the chip box itself is fixed-height with
       centered content, so selection changes color only — never size. */
    .muscle-option { height: var(--btn-h-sm); min-height: var(--btn-h-sm); padding: 4px 10px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); cursor: pointer; font-size: 12px; line-height: 1; text-transform: capitalize; display: inline-flex; align-items: center; justify-content: center; }
    .muscle-option-label { display: inline-flex; flex-direction: column; text-align: center; }
    .muscle-option-label::after { content: attr(data-label); font-weight: 800; height: 0; overflow: hidden; visibility: hidden; }
    .muscle-option[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
    .muscle-option:has(> svg) { display: inline-flex; align-items: center; gap: 6px; }
    .muscle-option > svg { width: 13px; height: 13px; fill: currentColor; flex: none; }
    select { width: 100%; min-height: var(--input-h); margin-top: 8px; padding: 0 12px; border: 1px solid var(--line); border-radius: var(--radius-md); background-color: var(--surface); color: var(--ink); font: inherit; }
    /* Every dropdown gets the theme-aware chevron (#9): never a white-on-white native arrow. */
    select { -webkit-appearance: none; appearance: none; padding-right: 34px; background-image: var(--chevron); background-repeat: no-repeat; background-position: right 12px center; }
    .summary-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin: 18px 2px 12px; }
    .library-section + .library-section { margin-top: 28px; }
    /* User 2026-09-12: a hairline separates Favorites from the Recents
       section below it. */
    .library-hairline { border: 0; border-top: 1px solid var(--line); margin: 24px 0 0; }
    .library-heading { margin: 0 2px 10px; font-size: 15px; letter-spacing: -.01em; }
    .result-count { margin: 0; font-size: 13px; color: var(--muted); }
    .active-query { font-weight: 700; color: var(--ink); }
    /* ===== 6. Exercise library cards & tags ===== */
    .exercise-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
    .exercise-card { position: relative; min-width: 0; border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface); transition: transform var(--motion-micro) var(--ease-standard), box-shadow var(--motion-micro) var(--ease-standard), border-color var(--motion-micro) var(--ease-standard); }
    .exercise-card-main { display: block; width: 100%; min-height: 164px; padding: var(--card-pad); padding-right: 56px; border: 0; background: none; color: inherit; font: inherit; text-align: left; cursor: pointer; }
    /* #491: the hover lift must only apply where hover really exists. iOS
       Safari keeps :hover stuck after a tap, so on touch the card jumped up
       2px on tap and stayed shifted (same sticky-:hover family as the
       dash-period-tabs fix). Matches the @media (hover: hover) guards on
       .picker-item:hover / .suggestion-card:hover / .calendar-page:hover. */
    @media (hover: hover) {
      .exercise-card:has(.exercise-card-main:hover) { transform: translateY(-2px); border-color: var(--line-strong); box-shadow: var(--shadow-soft); }
    }
    .exercise-card:has(.exercise-card-main:focus-visible) { outline: 3px solid var(--focus-ring); outline-offset: 2px; }
    .exercise-card-main:focus-visible { outline: none; }
    .exercise-card h2 { margin: 0 0 20px; font-size: 18px; line-height: 1.18; letter-spacing: -.02em; }
    /* Favorite star: hollow muted by default, filled accent when favorited. */
    .fav-toggle { display: grid; place-items: center; width: 44px; height: 44px; border: 0; border-radius: 50%; background: transparent; color: var(--muted); cursor: pointer; }
    .fav-toggle svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linejoin: round; }
    .fav-toggle:hover { background: var(--surface-2); }
    .fav-toggle[aria-pressed="true"] { color: var(--accent); }
    .fav-toggle[aria-pressed="true"] svg { fill: currentColor; }
    /* #390 (user 2026-09-13): the favorite star is part of the exercise
       header, not an afterthought — a bigger mark aligned with the title's
       first line. The 44px touch target is unchanged; the -6px nudge centers
       the star on the title text (30px title on phone, cap-height centered). */
    .detail-header .fav-toggle { align-self: start; margin-top: -6px; }
    .detail-header .fav-toggle svg { width: 30px; height: 30px; }
    .exercise-card .fav-toggle { position: absolute; top: 8px; right: 8px; }
    .tag-row { display: flex; flex-wrap: wrap; gap: 6px; }
    .tag { display: inline-flex; align-items: center; min-height: 24px; padding: 3px 8px; border-radius: 999px; background: var(--surface-2); color: var(--ink); font-size: 11px; text-transform: capitalize; }
    .tag.primary { background: var(--accent-soft); color: var(--accent); font-weight: 700; border: 1px solid var(--accent); }
    .tag.custom { background: var(--gold-soft); color: var(--warning); font-weight: 700; }
    /* Phase 2 (v1.901): "Single side" is a capability, not metadata — its own
       badge kind: soft-green fill, green border, uppercase micro-label. A NEW
       element; the base .tag pill treatment above is unchanged. */
    .tag.side { background: var(--accent-soft); border: 1px solid var(--accent); color: var(--accent-hover); font-weight: 700; font-size: 10px; letter-spacing: .05em; text-transform: uppercase; border-radius: var(--radius-sm); }
    .empty { padding: 60px 20px; text-align: center; color: var(--muted); grid-column: 1 / -1; }
    /* #504 (v1.8): shared empty states — the heading states the state, one
       short description, a primary action and an optional secondary link.
       Centered, quiet, and consistent across Library, Logs, Stats, and the
       program screens. */
    .empty-state { display: grid; gap: 10px; justify-items: center; text-align: center; padding: 34px 16px; }
    .empty-state-title { margin: 0; font: 700 22px/1.25 var(--font-sans); color: var(--ink); }
    .empty-state-sub { margin: 0; max-width: 340px; color: var(--muted); font-size: 13px; line-height: 1.5; }
    .empty-state-actions { display: grid; gap: 10px; justify-items: center; margin-top: 6px; }
    .empty-state-actions .primary-button { min-width: 210px; }
    /* #504: the Stats first-use state replaces the whole tab body. */
    #statsView.stats-is-empty > :not(#statsEmpty) { display: none; }
    #statsEmpty[hidden] { display: none; }
    /* #504: the no-active-program state sits above the (hidden) setup form. */
    #programEmpty { margin-bottom: 4px; }
    #programEmpty[hidden] { display: none; }
    /* #504: the workout-start hero speaks in the empty-state register —
       state-headed title, the pathways, a primary start action and a quiet
       secondary link into the saved list. */
    .training-hero-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 18px; margin-top: 20px; }
    .training-hero-actions .primary-button { min-width: 200px; }

    /* ===== 7. Dialogs & forms ===== */
    .custom-dialog { width: min(560px, calc(100% - 28px)); max-height: calc(100dvh - 28px); padding: 0; border: 0; border-radius: var(--radius-lg); background: var(--surface); color: var(--ink); box-shadow: var(--shadow); overflow: auto; scroll-padding-bottom: calc(88px + env(safe-area-inset-bottom)); }
    .custom-dialog::backdrop { background: var(--backdrop); }
    /* #620 (v1.908): sheet/dialog motion — the entrance is opacity plus a
       settle of at most 6px on the enter token (decelerate); the exit is
       opacity only on the exit token (accelerate), driven by the
       .is-closing class the dialog close path (programs.js) applies
       before the native close lands. Paint-only: the entrance animates an
       already-interactive dialog and never blocks input. */
    .custom-dialog[open] { animation: dialog-enter var(--motion-enter) var(--ease-decelerate); }
    @keyframes dialog-enter { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
    .custom-dialog.is-closing { animation: dialog-exit var(--motion-exit) var(--ease-accelerate); pointer-events: none; }
    @keyframes dialog-exit { from { opacity: 1; } to { opacity: 0; } }
    /* #179: share landing polish — a context line under the title, one
       primary action with a quiet text alternative (the twin full buttons
       read as redundant). #446 (user 2026-09-14): the #179 vertical centering
       is gone — it made the skeleton→hydrated swap jump, so the card is
       top-aligned and hydrates in place. */
    .share-context { margin: 0 0 10px; color: var(--muted); font-size: 13px; }
    .share-actions { display: grid; gap: 2px; justify-items: center; }
    .share-actions .primary-button { width: 100%; min-height: 52px; }
    /* Share landing header (user 2026-09-12): green Start button (#297) plus
       the bookmark icon next to the ×, so the actions are one tap away
       without scrolling. */
    .share-header-actions { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; }
    .share-icon-button { display: grid; place-items: center; width: var(--icon-btn); height: var(--icon-btn); border: 0; border-radius: var(--radius-md); background: transparent; color: var(--ink); cursor: pointer; padding: 0; }
    .share-icon-button:hover { background: var(--surface-2); }
    .share-icon-button:active { opacity: .6; }
    /* #433 (user 2026-09-14): back chevron on the drilled-in program workout
       card — matches the top bar's back-chevron language. */
    .share-back-btn { display: grid; place-items: center; width: var(--icon-btn); height: var(--icon-btn); margin-left: -12px; border: 0; border-radius: var(--radius-md); background: transparent; color: var(--ink); cursor: pointer; padding: 0; flex: 0 0 auto; }
    .share-back-btn svg { width: 22px; height: 22px; }
    .share-back-btn:active { opacity: .6; }
    /* #297 (user 2026-09-12): the header Start is a compact green button,
       sized to sit with the icon buttons. */
    .share-header-actions .share-start-btn { min-height: 40px; padding: 0 18px; font-size: 15px; }
    /* Share landing footer (user 2026-09-12): the full descriptive buttons —
       big primary pill + green text link + quiet grey note. */
    .share-footer-actions { margin-top: 20px; display: flex; flex-direction: column; gap: 8px; }
    /* #212 (user 2026-09-14): on the workout landing the same descriptive
       actions sit directly under the title/meta instead of in the footer. */
    .share-top-actions { margin: 2px 0 14px; display: flex; flex-direction: column; gap: 8px; }
    .share-alt-action { border: 0; background: none; padding: 10px 12px; min-height: 44px; color: var(--accent); font-size: 14px; font-weight: 700; cursor: pointer; }
    .share-alt-action:active { opacity: .6; }
    #sharePreviewBody { display: flex; flex-direction: column; min-height: calc(100dvh - var(--top-bar-clearance) - var(--bottom-nav-clearance)); }
    /* #446 (user 2026-09-14): the share card is top-aligned, not vertically
       centered. Centering made the skeleton→hydrated swap jump: the short
       skeleton rendered centered while the taller real card snapped to the
       top when its auto margins collapsed. Top-aligned, the card hydrates in
       place and only grows downward. */
    #sharePreviewBody .completed-card { margin-top: 0; margin-bottom: 0; }
    /* #307 (user 2026-09-13): the share landing no longer shows a bare
       "Loading…" card while the payload resolves — it renders the real card
       layout immediately with shimmer skeleton placeholders that hydrate in
       place, so nothing jumps when the content arrives. */
    @keyframes skel-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
    .skel { display: block; border-radius: 8px; background: linear-gradient(100deg, var(--surface-2) 40%, var(--line) 50%, var(--surface-2) 60%); background-size: 200% 100%; animation: skel-shimmer 1.4s linear infinite; pointer-events: none; user-select: none; }
    .skel-title { height: 34px; width: 62%; border-radius: var(--radius-md); }
    .skel-line { height: 14px; border-radius: 7px; }
    .skel-pill { height: 28px; width: 84px; border-radius: 999px; }
    .skel-row { min-height: 58px; border-radius: var(--radius-md); }
    .skel-btn { min-height: 52px; width: 100%; border-radius: var(--radius-md); }
    .skel-map { height: 190px; border-radius: var(--radius-md); margin-bottom: 12px; }
    .skel-circle { width: 40px; height: 40px; border-radius: 50%; }
    .skel-start { width: 88px; height: 40px; border-radius: var(--radius-md); }
    /* #409 (user 2026-09-14): skeleton sizes for the Stats view and the
       exercise-detail late-hydrating sections. Fixed heights match the real
       cards so hydration doesn't shift the layout. */
    .skel-stat-card { display: flex; flex-direction: column; gap: 10px; min-height: 94px; }
    .skel-stat-num { height: 30px; width: 55%; }
    .skel-stat-label { height: 12px; width: 80%; }
    .skel-stat-value { height: 30px; width: 60%; }
    .skel-stat-sub { height: 12px; width: 75%; }
    .skel-chart { height: 180px; border-radius: var(--radius-md); }
    @media (prefers-reduced-motion: reduce) { .skel { animation: none; } }
    /* #307 (user 2026-09-13): pre-paint share boot. The head script adds
       .share-boot to <html> when the URL is a share link, so the FIRST paint
       already shows the loading skeleton — the empty home dashboard never
       flashes before the scripts run. openSharePreviewLoading() removes the
       class when it renders the live skeleton (same synchronous task, so no
       paint lands between the static and live skeletons). Author styles beat
       the UA [hidden] rule, and the class is gone before any later
       show/hide, so the hidden attribute keeps working afterwards. */
    html.share-boot #dashboardView { display: none; }
    html.share-boot #workoutView { display: block; }
    html.share-boot #workoutView > :not(#sharePreview) { display: none; }
    html.share-boot #sharePreview { display: block; }
    .custom-form { padding: var(--space-5); }
    .custom-form-head { display: flex; align-items: start; justify-content: space-between; gap: 18px; margin-bottom: var(--space-5); }
    .custom-form-head h2 { margin: 0 0 5px; font: 700 24px/1.15 var(--font-sans); letter-spacing: -.02em; }
    .custom-form-head p { margin: 0; color: var(--muted); font-size: 13px; }
    .dialog-close { flex: 0 0 auto; display: grid; place-items: center; width: var(--icon-btn); height: var(--icon-btn); border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--muted); cursor: pointer; font-size: 22px; line-height: 1; }
    .form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; }
    .field.wide { grid-column: 1 / -1; }
    .field label { color: var(--muted); font-size: 12px; font-weight: 700; }
    .field input, .field textarea { width: 100%; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface-raised); color: var(--ink); outline: none; }
    .field input { min-height: var(--input-h); padding: 0 14px; }
    .field textarea { min-height: 96px; padding: var(--space-3) 14px; resize: vertical; }
    .field input:focus, .field textarea:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--focus-ring); }
    .field-help { color: var(--muted); font-size: 11px; line-height: 1.35; } /* #160: helper copy must wrap, never clip mid-word. */
    .field-help { overflow-wrap: break-word; }
    /* #205: reserve the tallest mode description (2 lines at 11px/1.35) so
       tapping a Progression mode pill swaps copy without a scroll jump. */
    #settingsSchemeDesc { min-height: 2.7em; }
    .settings-grid > *, .workout-meta > * { min-width: 0; } /* #160: grid children may shrink below content width so long help text wraps instead of overflowing. */
    fieldset.field { min-width: 0; margin: 0; padding: 0; border: 0; }
    fieldset.field legend { padding: 0; color: var(--muted); font-size: 12px; font-weight: 700; letter-spacing: .04em; }
    .form-pill-options { display: flex; flex-wrap: wrap; gap: 7px; }
    .form-pill { min-height: 36px; padding: 0 var(--space-3); border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); cursor: pointer; font-size: 12px; font-weight: 750; text-transform: capitalize; }
    .form-pill[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
    .form-pill:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: 2px; }
    .form-error { min-height: 18px; margin: 12px 0 0; color: var(--danger); font-size: 12px; }
    /* #145: action rows must wrap — long button labels (e.g. the review-sets
       dialog's four actions) overflowed the dialog at phone width. */
    .form-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; margin-top: 5px; }
    /* #244 (user 2026-09-12): share dialogs use two centered buttons, no
       Done — centered in both the row and compact-column layouts. */
    .form-actions.centered { justify-content: center; }
    .compact-dialog .form-actions.centered { flex-direction: row; align-items: center; }
    .compact-dialog .form-actions.centered .primary-button, .compact-dialog .form-actions.centered .secondary-button { flex: 1 1 0; }
    .builder-configure-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin: 8px 0 4px; }
    /* #343 (Phase 2, v1.901): dialog actions use ONE button family —
       .primary-button / .secondary-button (destructive = .primary-button
       .danger-button). #663: the .form-action legacy alias that survived
       here as a safety net was removed — no markup has used it since the
       #343 unification, and the versioned SW ships CSS + markup atomically,
       so no stray reference can exist. */
    /* #288: stacked sign-in choice options — title + explanatory sub-line. */
    .choice-list { display: grid; gap: 8px; margin: 12px 0 4px; }
    .choice-option { display: grid; gap: 2px; text-align: left; padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); cursor: pointer; }
    .choice-option strong { font-size: 15px; }
    .choice-option span { color: var(--muted); font-size: 12.5px; line-height: 1.4; }
    .choice-option:active { transform: scale(.99); }
    /* #415 (user 2026-09-13): the account-data option leads the sign-in
       choice — accent border, tinted background, Recommended pill — with an
       "or" hairline separating it from the destructive choices. */
    .choice-option.choice-recommended { position: relative; border: 2px solid var(--accent); background: color-mix(in srgb, var(--accent) 9%, var(--surface)); }
    .choice-option.choice-recommended strong { color: var(--accent); }
    /* #415 follow-up (user 2026-09-13): the Recommended pill sits badge-style
       on the option's top-right border edge, in the theme's readable
       on-accent color. */
    /* #415a (user 2026-09-13): the badge text must stay light and legible in
       every theme.
       #415 ROOT CAUSE (user 2026-09-13, v1.645): three builds changed this
       rule's color value and the badge still rendered gray. The rule was
       never winning: `.choice-option span { color: var(--muted) }`
       (specificity 0,1,1) beats `.recommended-badge` (0,1,0), so the pill
       always showed --muted. Fixed with a 0,2,1 selector plus !important —
       robustness beats elegance on the fourth attempt. User wants plain
       white text, full stop. */
    .choice-option span.recommended-badge { position: absolute; top: -11px; right: 12px; font-size: 10.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; color: #fff !important; background: color-mix(in srgb, var(--accent) 55%, #000); text-shadow: 0 1px 2px rgba(0,0,0,.35); border-radius: 999px; padding: 2px 9px; white-space: nowrap; }
    .choice-or { display: flex; align-items: center; gap: 10px; color: var(--muted); font-size: 12px; }
    .choice-or::before, .choice-or::after { content: ""; flex: 1; border-top: 1px solid var(--line); }
    .choice-confirm-copy { margin: 12px 0 4px; color: var(--muted); font-size: 13px; line-height: 1.5; }
    .choice-confirm-copy strong { color: var(--ink); }
    .choice-option.choice-danger { border: 2px solid var(--danger); }
    .choice-option.choice-danger strong { color: var(--danger); }
    .custom-tool { min-height: var(--btn-h-sm); padding: 0 12px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); cursor: pointer; font-size: 12px; font-weight: 700; }
    .custom-tool.danger { color: var(--danger); border-color: var(--danger); }
    .custom-tools-row { display: flex; gap: 10px; margin-top: 26px; }
    .custom-tools-row:empty { display: none; }
    .custom-tools-row .custom-tool { flex: 1; }
    .custom-delete-btn { width: 100%; min-height: 46px; font-size: 14px; }
    .empty strong { display: block; color: var(--ink); font: 700 22px/1.25 var(--font-sans); margin-bottom: 8px; }

    /* ===== 8. Exercise detail ===== */
    .detail { display: none; }
    .detail.active { display: block; }
    .library.hidden { display: none; }
    .back { margin: 0 0 var(--space-5); min-height: var(--btn-h-sm); padding: 0 14px 0 10px; border: 0; border-radius: var(--radius-md); background: var(--surface-2); cursor: pointer; display: inline-flex; align-items: center; gap: 4px; color: var(--ink); font-size: 14px; font-weight: 700; }
    .back:hover { background: var(--surface-2); color: var(--ink); }
    /* Top-bar back button (2026-09-11, user): plain chevron, same 44px
       footprint as the gear — no more pill. It inherits .top-bar-btn. */
    .detail-header { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 24px; align-items: start; margin-bottom: var(--space-5); }
    .detail-title { font: 800 clamp(30px, 6vw, 44px)/1.02 var(--font-sans); letter-spacing: -.03em; margin-bottom: 16px; }
    .detail-tags { display: flex; flex-wrap: wrap; gap: 7px; }
    .stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-3); margin-bottom: var(--space-5); }
    .stat { padding: var(--card-pad); border: 1px solid var(--line); background: var(--surface); border-radius: var(--radius-lg); }
    .stat-label { display: block; margin-bottom: 14px; color: var(--muted); font-size: 12px; font-weight: 700; }
    .stat-value { display: block; font-size: clamp(24px, 4vw, 34px); line-height: 1; font-weight: 750; letter-spacing: -.04em; font-variant-numeric: tabular-nums; }
    .stat-sub { display: block; margin-top: 7px; color: var(--muted); font-size: 12px; }
    .section { margin-bottom: var(--space-5); }
    .section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin-bottom: 12px; }
    .section h2 { margin: 0; font-size: 18px; letter-spacing: -.02em; }
    .section-note { margin: 0; color: var(--muted); font-size: 12px; }
    /* #14: compact segmented toggles (top-exercises metric, muscle-map mode).
       Fixed button widths so tapping never shifts the surrounding layout.
       v1.906: segmented controls are squared to the radius-md token —
       the pill radius stays reserved for switch tracks and progress. */
    .mini-segmented { display: inline-flex; gap: 3px; padding: 3px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); }
    .mini-segmented button { min-width: 56px; min-height: 36px; padding: 0 var(--space-3); border: 0; border-radius: var(--radius-md); background: transparent; color: var(--muted); font-size: 12px; font-weight: 750; cursor: pointer; box-shadow: none; }
    .mini-segmented button[aria-pressed="true"] { background: var(--accent-soft); color: var(--accent); box-shadow: none; }
    /* #4: upcoming program workout shown when a future calendar date is tapped. */
    .upcoming-kicker { margin: 0 0 4px; font-size: 12px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; color: var(--accent); }
    .upcoming-name { margin: 0 0 4px; font-size: 15px; }
    .upcoming-program .secondary-button { margin-top: 10px; }
    /* Exercise detail: stack section heads so notes never crowd the title on narrow screens. */
    .detail .section-head { flex-direction: column; align-items: flex-start; gap: 3px; }
    .detail .chart-empty { min-height: 0; padding: 30px 18px; }
    .detail .chart-wrap { min-height: 0; }
    /* Phone QA 2026-09-11: uniform card spacing across the exercise page. */
    #detailView > .dashboard-card { margin-bottom: 18px; }
    /* Detail sections are proper cards: heading lives inside, with real padding. */
    .history-list.is-empty { border-top: none; }
    .detail .history-empty { background: none; border-radius: 0; padding: 6px 2px 0; font-size: 13px; }
    /* #204 (user 2026-09-14): expanded per-workout history polish. Each
       session is a real card; the head row carries only date + View workout
       (the best-estimate headline moved to its own sub-line so nothing wraps
       awkwardly); set rows are hairline-separated lines, not inset
       mini-cards. */
    .history-list { border-top: none; display: grid; gap: 12px; align-content: start; }
    .history-list[hidden] { display: none; }
    .history-session { background: var(--surface); border-radius: var(--radius-lg); padding: 14px var(--space-4) var(--space-2); }
    .session-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
    .session-date { font-size: 14px; font-weight: 750; }
    .session-view { flex-shrink: 0; white-space: nowrap; }
    .session-sub { margin-top: 3px; font-size: 12px; color: var(--muted); }
    .history-list .exercise-tag-row { margin-top: 10px; }
    .history-list .sets { display: block; margin-top: 4px; }
    .set-row { display: grid; grid-template-columns: 48px 1fr 1fr 1fr; gap: 10px; align-items: center; min-height: 36px; padding: 0 10px; border-radius: var(--radius-sm); background: var(--surface); font-variant-numeric: tabular-nums; }
    .set-num { color: var(--muted); font-size: 11px; font-weight: 700; }
    /* Exercise history toggle + compact set rows (user 2026-09-12): history
       starts collapsed; each set is one tidy line. */
    .history-toggle { display: flex; align-items: center; justify-content: space-between; width: 100%; padding: 12px 2px; background: none; border: 0; border-top: 1px solid var(--line); color: var(--ink); font-size: 14px; font-weight: 700; cursor: pointer; }
    .history-toggle:active { opacity: .6; }
    .history-list .set-row { grid-template-columns: 28px 1fr auto; gap: 8px; background: none; border-radius: 0; padding: 8px 0; min-height: 0; border-top: 1px solid var(--line); }
    .history-list .set-row:first-child { border-top: none; }
    .history-list .set-num { font-size: 12px; }
    .history-list .set-main { font-size: 14px; }
    .history-list .set-main strong { font-size: 15px; }
    .history-list .set-meta { font-size: 12px; color: var(--muted); display: flex; align-items: center; gap: 6px; justify-content: flex-end; }
    .history-list .set-rpe strong { color: var(--ink); }
    .history-list .set-tag { display: inline-flex; align-items: center; min-height: 22px; padding: 2px 8px; border-radius: 999px; background: var(--surface-2); color: var(--ink); font-size: 11px; text-transform: capitalize; }
    .history-empty { padding: 30px 16px; color: var(--muted); background: var(--surface); border-radius: var(--radius-lg); }
    .instructions { padding-left: 20px; margin: 0; color: var(--ink); font-size: 13px; line-height: 1.55; }
    .instructions li + li { margin-top: 9px; }
    .similar-grid { display: block; }
    .score { color: var(--muted); font-size: 11px; }
    .formula { margin: 12px 0 0; color: var(--muted); font-size: 11px; line-height: 1.45; }
    /* ===== 9. Workout builder & live workout ===== */
    .workout-view, .program-view { display: none; }
    .workout-view.active, .program-view.active { display: block; }
    /* View transitions (#620, v1.908): opacity only — no translation or
       scaling on view switches (movement read as disorienting). The fade
       runs on the page token (120ms, standard easing) and never blocks
       input. The animation restarts every time the show function re-adds
       .active (hideAllViews removes it first); in-view sub-screens don't
       toggle .active, so they don't animate. Disabled for
       prefers-reduced-motion. */
    @keyframes view-enter { from { opacity: 0; } to { opacity: 1; } }
    .dashboard-view.active, .stats-view.active, .workout-view.active, .program-view.active,
    .detail.active, .library:not(.hidden) { animation: view-enter var(--motion-page) var(--ease-standard); }
    @media (prefers-reduced-motion: reduce) {
      .dashboard-view.active, .stats-view.active, .workout-view.active, .program-view.active,
      .detail.active, .library:not(.hidden), .ob-screen, .of-body { animation: none; }
    }
    .workout-start { max-width: 680px; padding: clamp(24px, 5vw, 48px); border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface); }
    .workout-start h2 { margin-bottom: 8px; font: 800 clamp(26px, 5vw, 34px)/1.08 var(--font-sans); letter-spacing: -.025em; }
    .workout-start p { max-width: 520px; margin-bottom: 24px; color: var(--muted); line-height: 1.5; }
    .workout-actions { display: flex; flex-wrap: wrap; gap: 9px; }
    /* Button variants — Phase 2 (v1.901) token taxonomy, superseding the
       v1.007 three-kinds note: (1) primary actions are squared radius-md
       at the tall 52px tier, flat (no shadow) — Finish, Start, Save, and
       EVERY dialog's main action (#343: one family in dialogs) (v1.906:
       primary buttons are no longer full pills); (2) secondary buttons are
       squared radius-md at the standard 44px tier — including every
       dialog's secondary action; (3) toggle/segmented controls (.form-pill,
       .step-pills, .reps-only-toggle) keep the prod pill treatment.
       CTA labels must fit: primary and secondary never wrap mid-label. */
    .primary-button, .secondary-button { min-height: var(--btn-h); padding: 0 18px; cursor: pointer; font-weight: 750; white-space: nowrap; text-align: center; }
    .primary-button { min-height: var(--btn-h-lg); padding: 0 22px; border: 1px solid var(--accent); border-radius: var(--radius-md); background: var(--accent); color: var(--on-accent); box-shadow: none; }
    .primary-button:hover { background: var(--accent-hover); }
    .secondary-button { border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink-strong); }
    .secondary-button:hover { filter: brightness(.96); }
    /* #112: bottom "Add exercise" button — full width like "Add set", with top spacing. */
    .add-exercise-bottom { display: flex; align-items: center; justify-content: center; width: 100%; margin: 18px 0 0; }
    .primary-button.danger-button { border-color: var(--danger); background: var(--danger); color: var(--on-accent); }
    /* #633 (user 2026-10-18): paired controls share ONE height — the 44px
       standard tier. Inside a dialog every primary drops to var(--btn-h)
       so pairs like Later/Refresh now render equal; page-level
       primaries outside dialogs keep the 52px hero tier. The same rule
       applied to the page-level action rows that pair a primary with a
       44px secondary: the session/builder footers (Discard + Finish /
       Save) and the prediction card (Use + Pick my own). */
    .custom-dialog .primary-button { min-height: var(--btn-h); }
    .workout-actions .primary-button { min-height: var(--btn-h); }
    .predict-actions .primary-button { min-height: var(--btn-h); }
    .secondary-button.danger-button { border-color: var(--rose); color: var(--rose); }
    .workout-meta { display: grid; grid-template-columns: 1fr; gap: 14px; margin-bottom: 22px; }
    /* #632 (user 2026-10-18): the meta fields' VALUES share one spec too —
       14px / 700, the FOCUS summary's treatment (see .workout-focus-summary
       and .date-display). The workout-name input was regular weight while
       focus/date rendered bold. Covers the session and builder name fields
       (both render inside .workout-meta). */
    .workout-meta .field input { background: var(--surface); font-size: 14px; font-weight: 700; }
    .workout-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 14px; margin-bottom: 12px; }
    .workout-toolbar h2 { margin: 0; font-size: 18px; }
    .workout-exercises { display: grid; gap: 14px; }
    .swipe-item { position: relative; overflow: hidden; border-radius: var(--radius-lg); background: var(--surface); }
    .swipe-content { position: relative; z-index: 1; transform: translateX(0); transition: transform var(--motion-enter) var(--ease-decelerate); touch-action: pan-y; }
    /* #107: width overlap removed — testing if will-change was the artifact source. */
    /* Delete rail: only the trailing 72px is red (user 2026-09-11) — the row
       slides left over it, iOS-Mail style. Icon-only, no text.
       #84 (user 2026-09-11, v1.007): the red is harmonized with the accent —
       --swipe-rail-bg is a per-theme explicit mix (danger 78% + accent 22%)
       so it reads destructive without clashing with the theme accent; the
       trash icon uses --on-accent (contrast >= 4:1 in every theme). */
    .swipe-delete-action { position: absolute; top: 0; bottom: 0; right: 0; width: var(--swipe-rail); border: 0; border-radius: 0 var(--radius-lg) var(--radius-lg) 0; background: var(--surface); -webkit-appearance: none; appearance: none; color: var(--on-accent); display: flex; align-items: center; justify-content: center; cursor: pointer; pointer-events: none; }
    /* #186: a closed rail sits behind the row content but still occupies its
       strip — without this it can win hit tests over a visible inline
       control at the same spot (e.g. the set-row ×). Only an open rail
       receives taps; the tabIndex is already managed the same way in
       setSwipeOpen(). */
    .swipe-item.is-open .swipe-delete-action { pointer-events: auto; }
    /* #107: closed rail matches the row surface — even if iOS WebKit blends at
       the rounded clip edge, it blends surface-with-surface (invisible).
       Red appears only while actively dragging (.is-swiping) or open. */
    .swipe-item.is-open .swipe-delete-action,
    .swipe-item.is-swiping .swipe-delete-action { background: var(--swipe-rail-bg); }
    .swipe-delete-action svg { width: 26px; height: 26px; display: block; }
    .workout-exercise { padding: 18px; border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface); }
    .workout-exercise.swipe-content { border: 0; }
    /* Collapsible exercise cards in the live workout */
    .workout-exercise.exercise-accordion { padding: 0; }
    /* #80 (user 2026-09-11 marathon): the collapse chevron sits to the LEFT
       of the exercise title, not in the top-right corner. */
    .exercise-accordion-head { display: flex; align-items: center; justify-content: flex-start; gap: 10px; padding: 15px 18px var(--space-4); cursor: pointer; list-style: none; }
    .exercise-accordion-head::-webkit-details-marker { display: none; }
    .exercise-accordion-title { display: grid; gap: 3px; min-width: 0; }
    .exercise-accordion-title strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 16px; letter-spacing: -.01em; }
    .exercise-accordion-title small { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }
    .exercise-accordion-chevron { flex: none; color: var(--muted); font-size: 20px; line-height: 1; transition: transform var(--motion-micro) var(--ease-standard); }
    /* #12 (user 2026-09-11, reverted 2026-09-13): the info button sits in the
       card header row, in line with the title and the Warm-up pill. (user
       2026-09-22/23: hidden on the collapsed summary via the :not([open])
       rule below — the v1.886 removal killed it on expanded cards too.) */
    .exercise-accordion.workout-exercise { position: relative; }
    .exercise-accordion.workout-exercise .exercise-accordion-head > .exercise-info-button { margin-left: auto; }
    /* (user 2026-09-13): when the Warm-up pill is present it takes the auto
       margin instead, so the pill sits snug against the info icon at the
       right edge — not floating mid-header wherever the title ends. When
       warm-ups exist the pill hides and the icon keeps its own auto margin. */
    .exercise-accordion.workout-exercise .exercise-accordion-head > .warmup-corner { margin-left: auto; }
    .exercise-accordion.workout-exercise[open] .exercise-accordion-head > .warmup-corner + .exercise-info-button { margin-left: 0; }
    /* #616 reverted (user 2026-10-18): the builder header mirrors the live
       card — the notes pencil sits left of the info icon; the pencil takes
       the auto margin and the info icon snugs against it. */
    /* The Warm-up pill and the info icon only make sense with the card open —
       hide them on the collapsed summary (user 2026-09-13 / 2026-09-22). */
    .exercise-accordion.workout-exercise:not([open]) .warmup-corner { display: none; }
    .exercise-accordion.workout-exercise:not([open]) .exercise-info-button { display: none; }
    /* QA batch (user 2026-09-22): the "i" glyph is the Lucide-style info icon
       stolen from the Kohlrabi trainer work — circle outline, stroked "i".
       The button centers the SVG; text sizing is gone. */
    /* FORK v1.953 (exercise-settings spec): ONE info-button spec — squared
       40px, token radius. The 32px circle is retired everywhere; the
       settings-title-row keeps its smaller contextual override below. */
    .exercise-info-button { position: relative; flex: none; width: 40px; height: 40px; border-radius: var(--radius-md); border: 1px solid var(--line); background: var(--surface); color: var(--muted); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; padding: 0; }
    .exercise-info-button svg { width: 18px; height: 18px; display: block; }
    .exercise-info-button::before { content: ""; position: absolute; left: 50%; top: 50%; width: 44px; height: 44px; transform: translate(-50%, -50%); }
    .exercise-info-button:active { transform: scale(.94); }
    /* v1.906: card-header icon buttons (notes pencil + info) are squared
       radius-md boxes. Scoped to the card heads so toolbar
       and standalone info buttons keep their own sizes.
       #611: their HEIGHT matches the Warm-up button (.warmup-corner:
       32px) — the 44px box towered over it. The base rule's 44px
       ::before tap target is unchanged. */
    .exercise-card-head .exercise-info-button, .exercise-accordion-head .exercise-info-button { width: 40px; height: 40px; border-radius: var(--radius-md); }
    .exercise-accordion[open] .exercise-accordion-chevron { transform: rotate(90deg); }
    .exercise-accordion-body { padding: 2px 18px 18px; }
    /* v1.900 cleanup: LIVE workout cards are no longer accordions — plain
       always-expanded cards (the builder still uses the accordion rules
       above). Header holds the title, Warm-up pill, notes icon, info icon. */
    /* v1.906: padding under the header row so the name/icon row breathes
       before the column labels / set rows — the builder summary head
       (.exercise-accordion-head) carries the same bottom padding. */
    .exercise-card-head { display: flex; align-items: center; gap: 10px; padding-bottom: var(--space-4); }
    .exercise-card-title { display: grid; gap: 3px; min-width: 0; }
    .exercise-card-title strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 16px; letter-spacing: -.01em; }
    .exercise-card-title small { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }
    .exercise-card-head > .warmup-corner { margin-left: auto; }
    .exercise-card-head > .exercise-info-button { margin-left: auto; }
    .exercise-card-head > .warmup-corner + .exercise-info-button { margin-left: 0; }
    /* The Exercise options disclosure takes the whole footer line; its
       body stacks inside the <details>, under the summary. */
    .exercise-options-row { display: flex; align-items: flex-start; gap: var(--space-2); margin-top: 12px; }
    .exercise-options-row > .advanced-options { flex: 1 1 auto; min-width: 0; margin-top: 0; }
    .exercise-card-body { padding-top: 2px; }
    /* v1.900: toolbar icon buttons are rounded squares (stroke icons). */
    .exercise-info-button.toolbar-icon-btn { width: 40px; height: 40px; border-radius: 10px; color: var(--ink); }
    /* v1.900: workout-focus disclosure — a full-width tappable row in the
       app's disclosure style; the presets live inside. */
    .workout-focus-disclosure { border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface); }
    .workout-focus-summary { position: relative; display: flex; align-items: center; justify-content: space-between; gap: 10px; min-height: 48px; padding: 10px 14px; color: var(--ink); font-size: 14px; font-weight: 700; cursor: pointer; list-style: none; }
    /* #613 (v1.907): floating legend on the field border, same notch pattern
       as .field > label (DATE / WORKOUT NAME) — the value sits inside the
       field, never a "Focus:" text prefix.
       #632 (user 2026-10-18): this legend's spec (12px / 700 / .04em,
       top -7px, left 8px, padding 0 6px, notch background) is now THE
       unified floating-legend spec — see the .field > label rule. */
    .workout-focus-legend { position: absolute; top: -7px; left: 8px; z-index: 1; padding: 0 6px; background: var(--notch-bg, var(--bg)); color: var(--muted); font-size: 12px; font-weight: 700; letter-spacing: .04em; }
    .workout-focus-summary::-webkit-details-marker { display: none; }
    .workout-focus-body { display: grid; gap: 8px; padding: 0 14px 12px; }
    /* v1.907: the Mixed marker — squared like the SINGLE SIDE badge family
       (.tag.side): soft accent fill, accent border, small uppercase label.
       Squared on purpose (radius token, never the pill radius); no chevron. */
    .mixed-marker { flex: none; margin-left: auto; padding: 2px 8px; border: 1px solid var(--accent); border-radius: var(--radius-sm); background: var(--accent-soft); color: var(--accent-hover); font: inherit; font-size: 10px; font-weight: 800; letter-spacing: .05em; text-transform: uppercase; cursor: pointer; }
    /* v1.907: mixed-ranges dialog rows — exercise name left, its effective
       range and source right; the source is small muted text. */
    .range-mix-list { display: grid; margin-bottom: 14px; }
    .range-mix-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 8px 0; border-top: 1px solid var(--line); }
    .range-mix-row:first-child { border-top: 0; }
    .range-mix-name { min-width: 0; font-weight: 700; color: var(--ink); }
    .range-mix-detail { display: flex; align-items: baseline; gap: 8px; text-align: right; }
    .range-mix-range { font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
    .range-mix-source { color: var(--muted); font-size: 11px; white-space: nowrap; }
    /* v1.900: warm-up ladder rung rows (tap-to-edit) in Settings. */
    .warmup-rung-list { display: grid; gap: 8px; margin-top: 8px; }
    .warmup-rung { border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); }
    .warmup-rung-row { display: flex; align-items: center; gap: 10px; width: 100%; min-height: 48px; padding: 10px 14px; border: 0; background: transparent; font: inherit; font-size: 14px; font-weight: 650; color: var(--ink); cursor: pointer; text-align: left; }
    .warmup-rung-values { margin-left: auto; color: var(--muted); font-weight: 550; font-variant-numeric: tabular-nums; }
    .warmup-rung-editor { display: flex; gap: 12px; padding: 2px 14px 14px; }
    .warmup-rung-editor .rule-field { flex: 1; }
    /* v1.900: the review dialog's per-set checklist label. */
    .review-list-label { margin: 2px 0 8px; font-size: 13px; font-weight: 650; color: var(--ink); }
    /* Pretty date display over a native date input */
    .date-field { position: relative; }
    .date-display { width: 100%; min-height: var(--input-h); padding: 0 14px; display: flex; align-items: center; justify-content: space-between; gap: 8px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); cursor: pointer; font: inherit; font-size: 14px; font-weight: 700; text-align: left; }
    .date-display:hover { border-color: var(--line-strong); }
    /* The native date input covers the pretty display invisibly, so tapping the
       field opens the OS date picker directly (reliable on iOS, unlike
       showPicker on a 1px hidden input). */
    .date-input-overlay { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; padding: 0; border: 0; opacity: 0; cursor: pointer; }
    /* v1.906: the native dropdown indicator is suppressed — no arrow is
       shown on the date field. Only the indicator is hidden; the overlay
       input itself still receives the tap and opens the picker. */
    input[type="date"]::-webkit-calendar-picker-indicator { display: none; }
    /* #183: the action column fits both 44px targets (check + delete) with
       room to spare — the old 50px column overlapped them on narrow rows. */
    .log-labels, .log-set { display: grid; grid-template-columns: 44px minmax(0,1fr) minmax(0,1fr) minmax(0,1fr) 96px; gap: 6px; align-items: start; }
    .log-labels { margin-bottom: 6px; color: var(--muted); font-size: 10px; font-weight: 750; }
    .log-labels span { text-align: center; white-space: nowrap; }
    /* QA batch (user 2026-09-22): the RPE "?" is gone — rule retired. */
    .log-set { padding: 12px 0; border: 0; background: transparent; }
    .log-set + .log-set { border-top: 1px solid var(--line); }
    .log-set-number { width: 44px; min-height: 44px; padding: 0; display: grid; place-items: center; border: 1px solid transparent; border-radius: var(--radius-md); background: transparent; color: var(--muted); cursor: pointer; font-size: 13px; font-weight: 800; font-variant-numeric: tabular-nums; }
    .log-set-number:hover, .log-set-number:focus-visible { border-color: var(--line); background: var(--surface-2); color: var(--ink); outline: none; }
    .log-set-number.has-tags { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
    .weight-entry { min-width: 0; display: grid; gap: 3px; }
    /* #146: dumbbell total readout under the weight field — "2 x per-hand =
       total" so a per-hand entry is visibly wrong. */
    .db-total-readout { font-size: 10.5px; line-height: 1.3; color: var(--muted); font-variant-numeric: tabular-nums; }
    .db-total-readout[hidden] { display: none; }
    .log-input { width: 100%; min-width: 0; height: var(--input-h-sm); padding: 0 9px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface-raised); color: var(--ink); outline: none; font-size: 16px; font-variant-numeric: tabular-nums; font-weight: 700; /* #234 (user 2026-09-12): #218's caret removal looked clean, but bring the caret back very light and theme-matched — a translucent accent wash, not the default dark blink. */ caret-color: color-mix(in srgb, var(--accent) 45%, transparent); }
    .log-input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--focus-ring); }
    /* #218: values render heavy like the exercise name; placeholders stay
       light so hints remain readable. */
    .log-input::placeholder { font-weight: 400; }
    .selected-set-tags { grid-column: 2 / -1; display: flex; flex-wrap: wrap; gap: 5px; min-height: 0; }
    .selected-set-tags:empty { display: none; }
    .set-tag-chip { display: inline-flex; align-items: center; min-height: 23px; padding: 3px 7px; border-radius: 999px; background: var(--accent-soft); color: var(--accent); font-size: 10px; font-weight: 700; }
    .set-actions { display: flex; align-items: center; justify-content: flex-end; gap: 5px; }
    /* Set-complete is an open checkbox now (user 2026-09-11, Strong-style):
       empty rounded box; accent fill + check when the set is done. */
    .complete-set { width: 44px; height: 44px; padding: 0; border: 0; background: transparent; cursor: pointer; display: grid; place-items: center; }
    .complete-set svg { width: 27px; height: 27px; display: block; }
    .complete-set .box { fill: none; stroke: var(--muted); stroke-width: 1.8; }
    .complete-set .tick { fill: none; stroke: transparent; stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round; }
    .complete-set[aria-pressed="true"] .box { fill: var(--accent); stroke: var(--accent); }
    .complete-set[aria-pressed="true"] .tick { stroke: var(--on-accent); }
    .log-set.is-complete .log-set-number { color: var(--accent); }
    /* Inline × delete (user 2026-09-11 marathon): quiet by default — small,
       muted, and visually subordinate to the checkbox; warms to a soft red
       only on hover/focus. Desktop keeps it; touch with swipe-to-delete on
       hides it (see the .is-touch.swipe-sets rule below). */
    /* #94: the button itself is the 44px target — the old 32px button relied
       on a 44px ::before hit area that overflowed the card edge and got clipped. */
    .delete-set-inline { position: relative; width: 44px; height: 44px; padding: 0; border: 0; border-radius: 50%; background: transparent; color: var(--muted); opacity: .5; cursor: pointer; display: grid; place-items: center; transition: opacity var(--motion-micro) var(--ease-standard), background-color var(--motion-micro) var(--ease-standard), color var(--motion-micro) var(--ease-standard); }
    .delete-set-inline svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; display: block; }
    .delete-set-inline:hover, .delete-set-inline:focus-visible { opacity: 1; background: var(--rose-soft); color: var(--danger); outline: none; }
    /* Swipe-to-delete replaces the × on touch devices when the Settings toggle
       is on (user 2026-09-11). Desktop always keeps the ×. */
    .is-touch.swipe-sets .delete-set-inline { display: none; }
    /* Program rows follow the same rule (user 2026-09-12): swipe-to-delete
       replaces the × when the Settings toggle is on; the × is the delete
       path when it's off. Desktop always keeps the ×. */
    .is-touch.swipe-sets .program-workout-del { display: none; }
    /* #324: saved-workout × follows the same rule — swipe replaces it. */
    .is-touch.swipe-sets .saved-workout-del { display: none; }
    .set-swipe { border-radius: 10px; }
    /* The delete action sits behind the row content: the row must be opaque
       (card surface), or the red rail bleeds through the row's transparent
       gaps and looks "stuck open" even when closed (user 2026-09-11). */
    .set-swipe .swipe-content { background: var(--surface); }
    .add-set { margin: 12px 0 0 50px; padding: 7px 0; border: 0; background: transparent; color: var(--accent); cursor: pointer; font-size: 13px; font-weight: 750; }
    .workout-footer { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-top: 20px; padding-top: 18px; border-top: 1px solid var(--line); }
    .workout-error { margin: 0; color: var(--danger); font-size: 12px; }
    .picker-search { width: 100%; height: var(--input-h); margin-bottom: 12px; padding: 0 12px; border: 1px solid var(--line); border-radius: var(--radius-md); outline: none; }
    /* Picker gets the library's search + filter interface (user 2026-09-10):
       circular icon-only filter button like the Exercises tab. */
    .picker-controls { display: grid; grid-template-columns: minmax(0,1fr) auto; gap: 8px; margin-bottom: 12px; align-items: center; }
    .picker-controls .picker-search { margin-bottom: 0; padding-left: 38px; padding-right: 44px; }
    .picker-filter-button { min-width: 0; width: 44px; height: 44px; padding: 0; border-radius: var(--radius-md); }
    .picker-filter-button span { display: none; }
    .picker-filter-panel { margin-bottom: 12px; }
    /* #619 (v1.908): the results list is a column flex, not a grid. As a
       grid inside the sheet's height-constrained flex column, a card whose
       content (name + the wrapping Custom-chip sub-row) outgrew its track
       painted past the track's bottom edge, and the next card — painted
       later, with an opaque surface — covered the overflow, cutting the
       previous card's bottom edge and its Custom chip off. Column-flex
       items lay out sequentially at their full content height, so cards
       can never stack over each other. */
    .picker-list { display: flex; flex-direction: column; gap: 7px; max-height: 48vh; overflow-y: auto; overscroll-behavior: contain; scroll-padding-bottom: calc(82px + env(safe-area-inset-bottom)); scroll-padding-top: 2px; }
    /* #510 (user 2026-09-16): fixed-size modal — the dialog frame never grows
       or shrinks as search results change, so the header, search, and Done
       button never move. The template-mode rules block gets its own scroller
       instead of squeezing the footer out of the fixed frame.
       user 2026-09-17: the rules block must NEVER shrink below its content —
       the old flex: 0 1 auto let the long results list's flex pressure squash
       it into a ~45px scroll-slice (selected exercise name scrolled out of
       view, the × clipped at the box edge). flex: 0 0 auto keeps it sized to
       its rows; the max-height cap still scrolls it internally when many
       exercises are selected.
       #675 (user 2026-10-18): the original split — everything flex-shrink: 0
       except the internally-scrolling results list — trapped the sheet when
       the filter panel expanded: the panel (single-column, taller than the
       whole sheet) could not shrink, had no scroll region, and squeezed the
       list to zero height inside the overflow:hidden frame, leaving the
       equipment selector and results unreachable.
       #678 (user 2026-10-19): the #675 fix first tried making the FORM the
       sheet's scroll container (list flex: 1 0 auto, overflow visible). It
       shipped broken: this comment's #675 paragraph was closed with a stray
       comment-close sequence that orphaned the user 2026-09-17 paragraph
       above as raw CSS text, which swallowed the #exercisePickerDialog
       frame rule below into an invalid selector — the sheet lost its fixed
       height, pins, and clip, and rendered as a lone floating footer. The
       model now: a dedicated
       .picker-scroll region between the fixed chrome (head, search,
       controls, rules block) and the footer is the sheet's one scroller,
       holding the filter panel + create-custom + results list. Its height
       comes from flexing inside the fixed frame (flex: 1 1 auto;
       min-height: 0), never from a percentage-height chain, and the list
       keeps its natural height inside it — so the expanded panel and the
       results scroll together and nothing is trapped (#675's outcome)
       without the form ever becoming a scroll container. */
    #exercisePickerDialog { top: 14px; bottom: 14px; height: min(640px, calc(100dvh - 56px)); overflow: hidden; }
    #exercisePickerDialog .custom-form { display: flex; flex-direction: column; height: 100%; }
    #exercisePickerDialog .custom-form > :not(.picker-scroll):not(#pickerTemplateOptions) { flex-shrink: 0; }
    #exercisePickerDialog #pickerTemplateOptions { flex: 0 0 auto; min-height: 0; overflow-y: auto; }
    #exercisePickerDialog .picker-scroll { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; scroll-padding-top: 2px; scroll-padding-bottom: 12px; }
    #exercisePickerDialog .picker-list { flex: none; max-height: none; overflow: visible; }
    /* #619: scroll-edge breathing room so the first card's top border and
       the last card (incl. its Custom chip) sit fully inside the list at
       both scroll extremes — the bottom pad already cleared the footer;
       the top pad matches it at the other edge. */
    #exercisePickerList { padding-top: 2px; padding-bottom: calc(76px + env(safe-area-inset-bottom)); }
    .picker-section-label { margin: 8px 2px 2px; color: var(--muted); font-size: 12px; font-weight: 800; display: flex; align-items: center; gap: 5px; letter-spacing: .08em; text-transform: uppercase; }
    /* #167: tappability + selection-count affordance under the picker title. */
    .picker-hint { margin: 3px 0 0; font-size: 11px; font-weight: 700; color: var(--muted); }
    .picker-hint strong { color: var(--accent); font-weight: 800; }
    /* #197 (user 2026-09-12): <button> with display:grid + width:auto
       shrink-wraps to its content in WebKit, so saved-workout detail rows
       (direct children of a block container, not a grid) collapsed to ragged
       widths. Full-bleed rows. */
    .picker-item { width: 100%; min-height: 58px; padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); cursor: pointer; text-align: left; display: grid; grid-template-columns: minmax(0,1fr) auto; gap: 12px; align-items: center; }
    .picker-item strong { display: block; margin-bottom: 4px; color: var(--ink); font-size: 14px; }
    /* Custom badge + meta share a wrapping flex row (user 2026-09-23): the
       badge no longer crowds the meta text inline, and long meta wraps
       instead of clipping at the row edge.
       User 2026-10-05 (#585): 6px read tight next to the 24px pill — 8px;
       badge rows also get 2px more top room so the pill doesn't crowd the
       exercise name. */
    .picker-item .picker-sub { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
    .picker-item .picker-sub:has(.tag) { margin-top: 2px; }
    .picker-item .picker-meta { color: var(--muted); font-size: 11px; text-transform: capitalize; }
    /* User 2026-09-23: create a custom exercise without leaving the workout
       picker — a quiet dashed affordance between the filters and the list.
       User 2026-10-05 (#585): 2px bottom margin nearly touched the first
       row — matches the 7px list rhythm now (8px optical). */
    .picker-create-custom { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; min-height: 44px; margin: 10px 0 8px; border: 1.5px dashed var(--line-strong); border-radius: var(--radius-md); background: none; color: var(--accent); font-size: 14px; font-weight: 700; cursor: pointer; }
    /* #619: the display:flex above would otherwise beat the UA [hidden]
       rule — the empty-search-only visibility toggle needs this to win. */
    .picker-create-custom[hidden] { display: none; }
    .picker-item .picker-state { color: var(--accent); font-size: 20px; font-weight: 800; }
    /* #619: picker rows in the sheet's list never shrink below their
       content and never clip it — each card takes its full height (name +
       chip row) and the next card starts after it. */
    #exercisePickerList .picker-item { flex: none; height: auto; overflow: visible; }
    /* #458 (user 2026-09-14): with several exercises selected the tray grows
       the dialog past its max-height, and the native <dialog> auto-centering
       parks the box above the viewport top — the header and first tray cards
       become permanently unreachable (no scroll gesture brings them back).
       Explicit top/bottom pins the box: a tall dialog anchors at the top and
       scrolls internally instead of escaping. #510 merged the pin into the
       fixed-frame rule above (top wins over bottom once height is fixed). */
    .picker-footer { position: sticky; bottom: -24px; margin: 14px -24px -24px; padding: 12px 24px calc(14px + env(safe-area-inset-bottom)); border-top: 1px solid var(--line); background: var(--surface); display: flex; justify-content: flex-end; }
    .dialog-empty { padding: 28px 8px; text-align: center; color: var(--muted); }
    .tag-dialog { width: min(430px, calc(100% - 28px)); }
    .tag-picker-options { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; }
    .tag-picker-options .form-pill { min-height: 40px; }
    .tag-list-editor { padding-top: 12px; border-top: 1px solid var(--line); }
    .tag-list-editor summary { color: var(--muted); cursor: pointer; font-size: 12px; font-weight: 700; }
    .tag-editor { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; margin: 12px 0 14px; }
    .tag-editor input { min-width: 0; height: var(--input-h-sm); padding: 0 11px; border: 1px solid var(--line); border-radius: var(--radius-md); outline: none; }
    .tag-list { display: flex; flex-wrap: wrap; gap: 7px; }
    .tag-list-item { display: inline-flex; align-items: center; gap: 5px; padding: 4px 6px 4px 9px; border-radius: 999px; background: var(--surface-2); font-size: 11px; }
    .tag-list-item button { position: relative; width: 22px; height: 22px; padding: 0; border: 0; border-radius: 50%; background: var(--line); color: var(--muted); cursor: pointer; }
    .tag-list-item button::before { content: ""; position: absolute; left: 50%; top: 50%; width: 44px; height: 44px; transform: translate(-50%, -50%); }
    .completed-card { max-width: 760px; padding: clamp(22px, 5vw, 38px); border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface); scroll-margin-bottom: var(--bottom-nav-clearance); }
    .completed-card h2 { margin-bottom: 6px; font: 800 clamp(26px, 5vw, 34px)/1.08 var(--font-sans); letter-spacing: -.025em; }
    .completed-meta { color: var(--muted); font-size: 13px; }
    /* #320 (user 2026-09-13): saved-workout detail summary renders as chips,
       not the "N exercises · M sets" text line.
       #363 (user 2026-09-13): the chips were cramped — larger gap, vertical
       breathing room above/below the row, and roomier chip padding so the
       text doesn't crowd the chip edges. */
    .meta-chips { display: flex; flex-wrap: wrap; gap: 10px; margin: 8px 0 12px; }
    .meta-chips .tag { min-height: 28px; padding: 5px 12px; font-size: 12px; }
    /* #375 (user 2026-09-13): dot-joined meta strings become chips or
       own-line subdued text instead of "A · B · C" clutter. */
    .empty-shell-hint { opacity: .72; }
    /* ===== 10. Programs ===== */
    .program-setup { max-width: 700px; padding: clamp(28px, 7vw, 44px) clamp(22px, 5vw, 42px) clamp(22px, 5vw, 42px); border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface); }
    .program-setup h2, .program-cover-head h2 { margin-bottom: 10px; font: 800 clamp(26px, 5vw, 34px)/1.08 var(--font-sans); letter-spacing: -.025em; }
    .program-setup > p { color: var(--muted); line-height: 1.5; }
    .program-form { display: grid; grid-template-columns: minmax(0,1fr) 150px; gap: 14px; margin-top: 22px; }
    .program-form .wide { grid-column: 1 / -1; }
    .program-form .field-help { margin: 6px 0 0; }
    /* #419 (user 2026-09-13): the create-program commit row — the error copy
       flexes, the button keeps its natural width instead of reading as a
       giant blob under a text wall. */
    .program-setup-actions { display: flex; align-items: center; gap: 14px; margin-top: 18px; flex-wrap: wrap; }
    .program-setup-actions .form-error { flex: 1 1 220px; margin: 0; }
    .program-setup-actions .primary-button { flex: 0 0 auto; }
    .program-cover { overflow: hidden; border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface); }
    /* Program cover header mirrors the Workout tab's "Start training" hero
       (user 2026-09-11): cream surface, green kicker, ink title. */
    .program-cover-head { padding: clamp(16px,4vw,26px); background: var(--surface); }
    .program-cover-head h2 { margin-bottom: 0; font-size: clamp(28px,5vw,44px); }
    /* #460 (user 2026-09-15): tighten the gap between the program name and
       the rotation info below it — the cover head already carries bottom
       padding, so the body's full top padding doubles it up. */
    .program-cover-head+.program-body { padding-top: 12px; }
    .program-cover-kicker { margin-bottom: 10px; color: var(--accent); font-size: 12px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; }
    .program-cover-meta { margin: 0; color: var(--muted); line-height: 1.5; }
    .program-body { padding: clamp(20px, 4vw, 34px); }
    .program-progress { display: flex; justify-content: space-between; gap: 12px; margin-bottom: 18px; color: var(--muted); font-size: 12px; }
    .program-workouts { display: grid; gap: 8px; margin: 12px 0 18px; }
    .program-workout { min-height: 62px; padding: 12px 14px; background: var(--surface); display: grid; grid-template-columns: 34px minmax(0,1fr); gap: 10px; align-items: center; }
    .program-workout strong { display: block; font-size: 14px; }
    .program-workout span { color: var(--muted); font-size: 11px; }
    .program-actions { display: flex; flex-wrap: wrap; gap: 9px; margin-top: 20px; }
    .session-note { margin: 12px 0 0; color: var(--muted); font-size: 11px; }
    /* ===== 11. Bottom nav & top bar ===== */
    .bottom-nav { position: fixed; left: 0; right: 0; bottom: 0; z-index: 20; width: 100%; margin: 0; display: grid; grid-template-columns: repeat(5, 1fr); padding: 6px 8px calc(6px + env(safe-area-inset-bottom)); border: 0; border-top: 1px solid var(--line); border-radius: 0; /* iOS 26 Safari ignores theme-color and samples fixed bars: keep an opaque background-color (user 2026-09-10) */ background-color: var(--bg); background-image: linear-gradient(var(--nav-bg), var(--nav-bg)); -webkit-backdrop-filter: blur(20px) saturate(1.6); backdrop-filter: blur(20px) saturate(1.6); }
    /* Slim persistent top bar: back chevron (non-root screens only), page title, settings gear. */
    .top-bar { position: fixed; top: 0; left: 0; right: 0; z-index: 30; display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; height: calc(54px + env(safe-area-inset-top)); padding: env(safe-area-inset-top) 4px 0; border-bottom: 1px solid var(--line); /* iOS 26 Safari ignores theme-color and samples fixed bars: keep an opaque background-color (user 2026-09-10) */ background-color: var(--bg); background-image: linear-gradient(var(--nav-bg), var(--nav-bg)); -webkit-backdrop-filter: blur(20px) saturate(1.6); backdrop-filter: blur(20px) saturate(1.6); /* 2026-09-11: the bar's own background now extends through the status-bar safe area (top:0 + padding-top) so the iOS status bar sits on the header color, not the page bg. Grid items lay out in the content box, so buttons stay centered in the 54px row. */ }
    #topBarBack { grid-column: 1; z-index: 1; }
    /* No reserved slot: the title is absolutely centered on the bar, so it stays
       pixel-static whether or not the back pill is shown (user 2026-09-10). */
    /* Bar-centered title: centered on the bar itself (not the leftover grid space),
       so back-button visibility can never shift it. Symmetric padding keeps long
       titles clear of the back button and gear while preserving the true center. */
    .top-bar-title { position: absolute; left: 0; right: 0; top: env(safe-area-inset-top); bottom: 0; display: flex; align-items: center; justify-content: center; margin: 0; padding: 0 56px; font-size: 22px; font-weight: 750; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; pointer-events: none; }
    /* Hidden title button (user 2026-09-12): on Log pages the header title
       jumps to the full log list — looks no different than other pages. */
    .top-bar-title .title-tap { font: inherit; color: inherit; background: none; border: 0; padding: 10px 14px; margin: 0; cursor: pointer; pointer-events: auto; display: inline-flex; align-items: center; gap: 6px; }
    #topBarSettings { grid-column: 3; z-index: 1; }
    /* Active state when the Settings page is open: same treatment as an active tab. */
    #topBarSettings.active { background: var(--accent-soft); color: var(--accent); }
    .top-bar-btn { display: grid; place-items: center; width: 44px; height: 44px; border: 0; border-radius: 12px; background: transparent; color: var(--ink); cursor: pointer; }
    .top-bar-btn svg { width: 22px; height: 22px; stroke-width: 2.5; /* match tab icon weight (user 2026-09-10) */ }
    .top-bar-btn[hidden] { display: none; }
    .top-bar-left { display: flex; align-items: center; gap: 8px; grid-column: 1; }
    .top-bar-right { display: flex; align-items: center; gap: 8px; grid-column: 3; }
    /* Live-workout chip (user 2026-09-11): top-right header shortcut back to a
       live session, shown whenever a session exists and we're not on the live
       editor page. */
    .live-chip { display: inline-flex; align-items: center; gap: 7px; min-height: 32px; padding: 3px 10px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); font-size: 13px; font-weight: 700; white-space: nowrap; cursor: pointer; }
    .live-chip[hidden] { display: none; }
    .live-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); animation: live-pulse 1.6s ease-in-out infinite; }
    @keyframes live-pulse { 0%,100% { opacity: 1; } 50% { opacity: .3; } }
    @media (prefers-reduced-motion: reduce) { .live-dot, .live-title-dot { animation: none; } }
    .nav-item { min-height: 50px; border: 0; border-radius: 12px; background: transparent; color: var(--muted); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px; cursor: pointer; font-size: 10px; font-weight: 700; line-height: 1.25; }
    /* #325 (user 2026-09-12): no accent tint on the Workout tab while a draft
       is live — the tab reads active only when it's the current page. The
       header Live chip remains the way back to the session. */
    /* Live-session dot next to the header title on the live editor page
       (user 2026-09-12): the chip's pill disappears there and its dot moves
       here. Same pulse as the chip dot; hidden unless a session is live.
       #313 (user 2026-09-12): absolutely positioned against the title text —
       in the flex flow it nudged "Workout" left of center on the live
       editor. The title is now dead-center on every screen, as on the
       workout home screen. */
    #topBarTitleText { position: relative; }
    .live-title-dot { position: absolute; left: 100%; top: 50%; transform: translateY(-50%); width: 8px; height: 8px; margin-left: 9px; border-radius: 50%; background: var(--accent); animation: live-pulse 1.6s ease-in-out infinite; }
    .live-title-dot[hidden] { display: none; }
    /* A dot clone flies from the chip to the title on chip tap — created and
       animated imperatively, removed when the flight ends. */
    .live-dot-fly { position: fixed; z-index: 60; margin: 0; pointer-events: none; }
    .nav-item[disabled] { opacity: .42; cursor: default; }
    .small-button[disabled] { opacity: .45; cursor: default; }
    .nav-item svg { width: 24px; height: 24px; stroke-width: 2.5; }
    /* Workout barbell sits at a slight angle for a more dynamic mark. Transform-only: no layout shift. */
    #workoutsNav svg { transform: rotate(-16deg); }
    /* Press feedback for touch (PWA has no haptics on iOS). Transform-only: never shifts layout. */
    .nav-item, .day-chip, .period-tab, .muscle-option, .form-pill, .recent-workout { transition: transform var(--motion-micro) var(--ease-standard); }
    @media (hover: none) {
      /* User 2026-09-12: the press-scale on tab icons reads as a glitchy
         "icon shift" (most visible on Program, whose heavy render delays the
         paint past the press). Tabs keep the instant active-pill feedback. */
      .day-chip:active, .period-tab:active, .muscle-option:active, .form-pill:active, .recent-workout:active { transform: scale(.96); }
    }
    .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }

    @container (max-width: 820px) {
      .exercise-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    }
    @container (max-width: 580px) {
      .shell { padding: 20px 16px 112px; }
      .add-exercise { min-height: 44px; padding: 0 11px; font-size: 13px; }
      .custom-form { padding: 20px 16px; }
      .form-grid { grid-template-columns: 1fr; }
      .field.wide { grid-column: auto; }
      .controls { grid-template-columns: 1fr auto; gap: 8px; }
      .filter-button { min-width: var(--input-h); width: var(--input-h); padding: 0; }
      .filter-button span { display: none; }
      .filter-panel { grid-template-columns: 1fr; }
      .muscle-options { gap: 6px; }
      .muscle-option { min-height: var(--btn-h-sm); }
      .exercise-grid { grid-template-columns: 1fr; gap: 8px; }
      /* QA batch (user 2026-09-21, #4): a single sparse search result
         rendered as a tall empty card (min-height 128px + big padding).
         On phone the grid is one column, so cards read as rows — drop the
         fixed height and tighten the rhythm. */
      .exercise-card-main { min-height: 0; padding: 12px; padding-right: 50px; }
      .exercise-card h2 { margin-bottom: 8px; font-size: 16px; }
      .detail-header { grid-template-columns: minmax(0, 1fr) auto; gap: 12px; }
      .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
      .stat:first-child { grid-column: 1 / -1; }
      .stat { padding: 16px; }
      .set-row { grid-template-columns: 36px repeat(3, 1fr); gap: 5px; padding: 0 8px; }
      .workout-meta { grid-template-columns: 1fr; }
      .workout-meta .date-field { grid-column: auto; grid-row: auto; }
      .workout-toolbar { align-items: flex-end; }
      .workout-exercise { padding: 15px 12px; }
      .log-labels { display: grid; grid-template-columns: 44px repeat(3, minmax(0, 1fr)) 96px; gap: 6px; }
      /* A1 (pixel-peeper PP1): explicit header placements mirroring the body
         inputs, so the RPE header can never drift off its input column. */
      .log-labels span:nth-child(1) { grid-column: 1; }
      .log-labels span:nth-child(2) { grid-column: 2; }
      .log-labels span:nth-child(3) { grid-column: 3; }
      .log-labels span:nth-child(4) { grid-column: 4; }
      .log-labels span:nth-child(5) { grid-column: 5; }
      .log-set { grid-template-columns: 44px repeat(3, minmax(0, 1fr)) 96px; gap: 6px; padding: 12px 0; border-top: 1px solid var(--line); }
      /* #183: the 96px action column fits the 44px check + 44px inline
         delete without overlap; on touch with swipe-delete on, the inline
         delete is hidden (.is-touch.swipe-sets .delete-set-inline), so the
         50px column suffices for the check alone and the inputs get room. */
      .is-touch.swipe-sets .log-set, .is-touch.swipe-sets .log-labels { grid-template-columns: 44px repeat(3, minmax(0, 1fr)) 50px; }
      .log-set-number { grid-row: 1; grid-column: 1; }
      .log-set .weight-entry { grid-row: 1; grid-column: 2; }
      .log-set .reps-input { grid-row: 1; grid-column: 3; }
      .log-set .rpe-input { grid-row: 1; grid-column: 4; }
      .log-set .complete-set { grid-row: 1; grid-column: 5; }
      .selected-set-tags { grid-row: 2; grid-column: 2 / 6; }
      .log-input { padding: 0 6px; text-align: center; }
      /* User 2026-09-13: placeholder size back to the old 10px shrink. */
      .log-input::placeholder { font-size: 10px; }
      .add-set { margin-left: 50px; }
      .workout-footer { align-items: flex-start; flex-direction: column; }
      .workout-footer .workout-actions { width: 100%; }
      .workout-footer .workout-actions button { flex: 1; }
      .program-form { grid-template-columns: 1fr 1fr; }
      .program-form .wide { grid-column: 1 / -1; }
      .picker-footer { bottom: -20px; margin: 14px -16px -20px; padding-left: 16px; padding-right: 16px; }
      .picker-footer button { width: 100%; }
    }
    @media (prefers-reduced-motion: reduce) {
      /* #620: the motion tokens themselves collapse too, so any surface
         (including JS reading the tokens) sees ~0ms under reduced motion. */
      :root { --motion-page: 0ms; --motion-enter: 0ms; --motion-exit: 0ms; --motion-micro: 0ms; }
      *, *::before, *::after { scroll-behavior: auto !important; transition: none !important; animation: none !important; }
    }

    /* Mobile-first training workspace refinements */
    input[type="number"] { appearance: textfield; -moz-appearance: textfield; }
    input[type="number"]::-webkit-inner-spin-button,
    input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
    /* ===== 12. Dashboard & stats ===== */
    .dashboard-view, .stats-view { display: none; }
    .dashboard-view.active, .stats-view.active { display: block; }
    .week-strip { display: grid; grid-template-columns: repeat(7, minmax(0,1fr)); gap: 7px; margin: 0 0 8px; touch-action: pan-y; }
    .day-chip { min-width: 0; padding: 8px 2px; border: 1px solid var(--line); border-radius: 18px; background: var(--surface); text-align: center; }
    .day-chip span, .day-chip strong { display: block; }
    .day-chip span { color: var(--muted); font-size: 10px; font-weight: 750; text-transform: uppercase; }
    .day-chip strong { margin-top: 4px; font-size: 14px; }
    .day-chip.today { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
    .day-chip.has-workout strong::after { content: ''; display: block; width: 4px; height: 4px; margin: 4px auto -2px; border-radius: 50%; background: var(--accent); }
    .dashboard-grid { display: grid; grid-template-columns: minmax(0,1.3fr) minmax(260px,.7fr); gap: 14px; }
    .dashboard-card { padding: var(--card-pad); border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface); }
    .dashboard-card.wide { grid-column: 1 / -1; }
    .dashboard-card h2 { margin: 0 0 12px; font-size: 17px; }
    .dashboard-card p { margin-bottom: 14px; color: var(--muted); font-size: 13px; line-height: 1.5; }
    /* #233 (user 2026-09-13): the Active Program card's quick-jump action
       sits side by side with Open program, wrapping on narrow cards. */
    .dash-program-actions { display: flex; flex-wrap: wrap; gap: 8px; }
    .dash-program-actions .primary-button { white-space: normal; max-width: 100%; }
    /* #436 (user 2026-09-14): the quick-start is the program-next card now —
       its own green border carries the emphasis, so the #417 outline ring
       is retired. */
    /* User 2026-09-14: the empty-state hero for accounts with no workout
       data — headline, a Start-workout card in the program-next styling,
       an optional Sign-in button, and the bold guide link. */
    /* user 2026-09-16: the hero is the Workout tab's no-data card register
       (section.training-hero) — left-aligned, the card carries its own
       padding. */
    .dash-empty-hero { padding: 2px 0 6px; }
    .dash-empty-hero[hidden] { display: none; }
    .dash-empty-hero .training-hero { margin: 0; }
    .dash-empty-hero .training-hero-actions .primary-button { min-width: 200px; }
    .dash-empty-hero .continue-workout-card { width: 100%; }
    .dash-empty-hero .workout-start-options { margin-top: 18px; }
    .dash-empty-signin { min-width: 200px; margin-top: 18px; }
    .dash-empty-guide { margin: 14px 0 0; font-size: 15px; }
    /* #437 (user 2026-09-14): fresh accounts see ONLY the Ready? hero —
       the calendar strip and the dashboard cards below stay hidden until
       the first workout is logged. */
    #dashboardView.dash-is-empty .calendar-toolbar,
    #dashboardView.dash-is-empty #weekStrip,
    #dashboardView.dash-is-empty .dashboard-grid { display: none; }
    .mini-stats { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 8px; }
    .recent-workouts { display: grid; gap: 8px; }
    .recent-workout { width: 100%; padding: 12px 0; border: 0; border-bottom: 1px solid var(--line); background: transparent; text-align: left; cursor: pointer; display: flex; justify-content: space-between; gap: 12px; }
    .recent-workout:last-child { border-bottom: 0; }
    .recent-workout span { color: var(--muted); font-size: 12px; }
    .recent-workout strong { display: block; font-size: 15px; font-weight: 800; color: var(--ink); }
    .history-search { margin-bottom: 16px; }
    .history-search input { width: 100%; min-height: var(--input-h); padding: 0 16px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); font-size: 16px; }
    .history-search input::placeholder { color: var(--muted); }
    .stats-grid { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 12px; margin-bottom: 20px; }
    .stats-panel { padding: 18px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); }
    /* #223 (user 2026-09-12): optical alignment — a large numeral's left glyph
       side-bearing (worst on a leading "1") pushes the number's ink right of
       the label's ink even though both boxes are left-aligned. Nudge the
       number back so it reads aligned with the card label. Em-based so it
       scales with the number size (30px Stats, 24px dashboard). */
    /* User 2026-09-22: stat numbers 30px -> 26px — "98,780" crowded its
       card at 30px. All three cards share the class so they stay consistent;
       the tight letter-spacing and optical left-alignment nudge stay. */
    .stats-panel strong { display:block; font-size: 26px; letter-spacing: -.04em; margin-left: -.08em; }
    .stats-panel span { color: var(--muted); font-size: 12px; }
    /* User 2026-09-12: "See all" right-justified on the recent-workouts card. */
    .see-all-link { border: 0; background: none; padding: 4px 0 4px 12px; color: var(--accent); font-size: 14px; font-weight: 700; cursor: pointer; white-space: nowrap; }
    .see-all-link:active { opacity: .6; }
    /* #128: the Completed-workouts stat panel doubles as the Stats-tab link
       to the workout logs — reset button chrome so it reads as a stat. */
    .stats-panel-link { font: inherit; color: inherit; text-align: left; cursor: pointer; width: 100%; }
    /* Compact stat numbers (user 2026-09-12): tapping an abbreviated card
       reveals the true total in smaller text; tapping again restores.
       The tapped size must beat the #dashboardStats ID selector below;
       the full number never wraps or cuts off (JS shrink-to-fit), and its
       box stays locked to the abbreviated height so the label never moves
       and the number stays in line with the other cards. */
    .stat-toggle strong { white-space: nowrap; }
    #dashboardStats .stat-toggle.show-full strong,
    .stats-grid .stat-toggle.show-full strong { font-size: 14px; letter-spacing: 0; }
    .stats-panel-link:active { border-color: var(--accent); }
    .swipe-item.is-open .swipe-content { transform: translateX(calc(-1 * var(--swipe-rail))); }
    .exercise-tools { display: flex; align-items: center; gap: 6px; }
    /* #438 (user 2026-09-14): one subtle outline per superset group replaces the per-card band. */
    .superset-group { display: grid; gap: 6px; border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 8px; }
    .superset-group-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
    .superset-group-label { font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); }
    .superset-group-edit { border: 0; background: transparent; color: var(--accent); font-size: 12px; font-weight: 750; padding: 8px 4px; cursor: pointer; }
    .exercise-note { margin-top: 14px; }
    .add-note-toggle { display: inline-flex; align-items: center; min-height: 44px; padding: 6px 0; border: 0; background: transparent; color: var(--accent); cursor: pointer; font-size: 12px; font-weight: 750; }
    .exercise-note textarea { width: 100%; min-height: 62px; padding: 10px; border: 1px solid var(--line); border-radius: var(--radius-sm); resize: vertical; outline: none; }
    .exercise-note textarea:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--focus-ring); }
    .small-button { min-height: var(--btn-h-sm); padding: 0 14px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); cursor: pointer; font-size: 12px; font-weight: 750; }
    /* F2 (pixel-peeper PP2): anchors can carry the pill treatment too
       (About → Report a bug) — inline-flex so the padding/height apply. */
    a.small-button { display: inline-flex; align-items: center; text-decoration: none; color: var(--ink); }
    .status-note { min-height: 18px; margin: 8px 0 0; color: var(--accent); font-size: 12px; }
    .status-note.is-error { color: var(--danger); }
    .bottom-nav { grid-template-columns: repeat(5, 1fr); }
    @container (max-width: 700px) {
      .dashboard-grid { grid-template-columns: 1fr; }
      .dashboard-card.wide { grid-column: auto; }
      /* User 2026-09-12: 3 stat cards across at every width — compact
         "1M+"/"10K+" numbers keep long totals inside their own third, so the
         old 2+1 overflow treatment is gone. */
      .stats-grid { grid-template-columns: repeat(3,minmax(0,1fr)); }
      .mini-stats { grid-template-columns: repeat(3,minmax(0,1fr)); }
    }
    @container (max-width: 580px) {
      .shell { padding-left: 12px; padding-right: 12px; }
      .week-strip { gap: 4px; }
      .day-chip { border-radius: 16px; }
      .dashboard-card { padding: 16px; }
      .log-set .set-actions { grid-column: 5; }
      .exercise-tools { flex-wrap: wrap; justify-content: flex-start; }
      .bottom-nav { padding: 4px 6px calc(4px + env(safe-area-inset-bottom)); }
      .nav-item { min-height: 48px; font-size: 10px; }
      .nav-item svg { width: 22px; height: 22px; }
    }

    /* Interaction polish: stable tap states, native-feeling delete rails, and dashboard controls */
    [hidden] { display: none !important; }
    button { -webkit-tap-highlight-color: transparent; }
    button:active { transform: none; }
    .day-chip { width: 100%; color: inherit; cursor: pointer; appearance: none; }
    .day-chip.selected { border-color: var(--accent); background: var(--accent); color: var(--on-accent); }
    .day-chip:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: 2px; }
    .dashboard-card-head { position: relative; display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
    .dashboard-card-head h2 { margin: 0; }
    /* Home streak (user 2026-09-19): consecutive training days, in the
       At-a-glance header. Shown only when the streak is ≥ 2 days. */
    .streak-chip { display: inline-flex; align-items: baseline; gap: 6px; padding: 5px 10px; border: 1px solid var(--line); border-radius: 999px; background: var(--surface-2); white-space: nowrap; }
    .streak-chip strong { font-size: 13px; letter-spacing: -.02em; }
    .streak-chip small { color: var(--muted); font-size: 10px; text-transform: uppercase; letter-spacing: .06em; }
    /* Sticky-:hover fix (user 2026-09-11): on touch devices iOS keeps :hover
       applied after a tap, so a hover style that matches the selected style
       leaves cards looking "stuck" selected. :hover only applies where hover
       really exists; the selected state stays unconditional. */
    .dash-period-tabs { margin: 0 0 14px; }
    #dashboardStats .stats-panel { padding: 14px 8px; }
    #dashboardStats .stats-panel strong { font-size: 24px; }
    /* #149 (user 2026-09-12): stat labels stay on one line — 10px + nowrap.
       Labels are the short forms ("Workouts" / "Sets" / "Volume (lb)"): the
       full "Completed workouts" measures ~107px on iOS and cannot fit the
       ~91px card content at any readable size, so it spilled past the card. */
    #dashboardStats .stats-panel span { font-size: 10px; white-space: nowrap; }
    .muscle-summary { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
    /* #19: the summary ("This week." / "Go to this week." / selected-date line)
       lives in the toolbar, right under the week label between the arrows.
       min-height keeps the toolbar from shifting when the line swaps. */
    .calendar-summary { min-height: 19px; margin: 3px 0 0; color: var(--muted); font-size: 12px; text-align: center; }
    .link-button { padding: 0; border: 0; background: none; color: var(--accent); cursor: pointer; font: inherit; font-weight: 700; }
    /* #415b (user 2026-09-13): the destructive-confirm "Back" is smallish
       bold link text below the destructive button — same behavior, new
       presentation. */
    .form-actions-center { justify-content: center; }
    .signin-choice-nevermind { font-size: 13px; }
    /* ===== 13. Charts ===== */
    .chart-card { margin-top: 12px; }
    .chart-wrap { width: 100%; overflow: hidden; }
    /* Phone-first 360x210 viewBox (user 2026-09-12): height follows the
       aspect ratio, so the plot fills the card — no dead bands. */
    .chart-wrap svg { display: block; width: 100%; height: auto; }
    .chart-grid { stroke: var(--line); stroke-width: 1; opacity: .55; }
    .chart-line { fill: none; stroke: var(--accent); stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }
    .chart-area { fill: var(--accent); opacity: .12; }
    .chart-dot { fill: var(--surface); stroke: var(--accent); stroke-width: 2; }
    .chart-label { fill: var(--muted); font: 10px var(--font-sans); font-variant-numeric: tabular-nums; }
    .chart-empty { min-height: 160px; display: grid; place-items: center; color: var(--muted); font-size: 13px; text-align: center; }
    /* #8: exercise-trend picker chips + bar chart + kickers. */
    .chart-kicker { margin: 14px 0 2px; font-size: 12px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
    .chart-kicker:first-child { margin-top: 0; }
    /* #309 (user 2026-09-12): the 1RM/Heaviest toggle sits on the chart's own
       header, right-justified. */
    /* #318 refinement (user 2026-09-12): the kicker + headline form one left
       block with the toggle vertically centered against the whole block — the
       toggle no longer floats on the kicker row alone. */
    .chart-head-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 10px 0 0; }
    .chart-head-row .chart-kicker { margin: 0; }
    .chart-head-text { min-width: 0; }
    .chart-head-text .chart-headline { margin: 2px 0 6px; }
    /* #318: hairline separator between the progress chart and the
       volume / time-under-tension chart (only when the second chart exists). */
    #exerciseVolumeChart:not(:empty) { border-top: 1px solid var(--line); margin-top: 14px; padding-top: 14px; }
    .chart-bar { fill: var(--accent-soft); stroke: var(--accent); stroke-width: 1.5; }
    /* Tappable charts (user 2026-09-12): invisible hit areas + readout. */
    .chart-hit { cursor: pointer; }
    .chart-dot.is-selected { fill: var(--accent); }
    .chart-bar.is-selected { fill: var(--accent); }
    /* Hevy-style fixed headline (user 2026-09-12): big value + date, always
       present with reserved space — tapping a point only swaps the text, so
       the chart can never jump. */
    .chart-headline { margin: 2px 0 6px; min-height: 34px; display: flex; align-items: baseline; gap: 8px; font-variant-numeric: tabular-nums; }
    .chart-headline strong { font-size: 26px; font-weight: 800; letter-spacing: -.02em; color: var(--ink); }
    .chart-headline span { font-size: 14px; font-weight: 600; color: var(--accent); }
    /* Progress metric toggle (user 2026-09-12): the tappable headline. */
    .chart-headline.metric-toggle { cursor: pointer; }
    /* #7: muscle drill-down — rows become tappable, expanding to the
       exercises driving that muscle's volume. */
    .muscle-volume-group { border-bottom: 1px solid var(--line); }
    .muscle-volume-group:last-child { border-bottom: 0; }
    button.muscle-volume-row { width: 100%; padding: 9px 2px; border: 0; background: transparent; color: var(--ink); font: inherit; text-align: left; cursor: pointer; grid-template-columns: minmax(82px,.65fr) minmax(100px,1fr) auto 14px; }
    .muscle-drilldown { padding: 2px 2px 10px 6px; display: grid; gap: 2px; }
    .drilldown-row { display: grid; grid-template-columns: 1fr auto 14px; gap: 8px; align-items: center; width: 100%; padding: 8px 6px; border: 0; border-radius: 8px; background: transparent; color: var(--ink); font-size: 13px; text-align: left; cursor: pointer; }
    .drilldown-row:active { background: var(--surface); }
    .drilldown-row > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .drilldown-value { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; white-space: nowrap; }
    .drilldown-row > span[aria-hidden] { color: var(--muted); }
    .program-workout { grid-template-columns: 34px minmax(0,1fr) auto; }
    .week-progress { display: grid; grid-template-columns: repeat(var(--program-weeks, 8), minmax(0,1fr)); gap: 4px; margin: 12px 0 20px; }
    .week-segment { height: 6px; border-radius: 999px; background: var(--surface-2); }
    .archived-programs { margin-top: 18px; }
    /* #455 (user 2026-09-14): the Saved programs list mirrors the Archived
       programs card — same rows, same separators, same action pill. */
    .saved-programs { margin-top: 18px; }
    /* Tapping a row opens the saved program; the button resets to plain text. */
    .open-saved-program { flex: 1; min-width: 0; background: none; border: 0; padding: 0; margin: 0; text-align: left; color: inherit; font: inherit; cursor: pointer; }
    /* The Shared chip keeps its accent styling inside the muted archive row. */
    .archive-row .built-in-label { color: var(--accent); font-size: 10px; }
    .archive-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 13px 0; border-top: 1px solid var(--line); }
    .archive-row span { color: var(--muted); font-size: 11px; }
    @container (max-width: 580px) {
      .program-workout { grid-template-columns: 30px minmax(0,1fr) auto; padding: 10px; }
    }

    /* Extended calendar and muscle-volume maps */
    .week-strip { display: grid; grid-template-columns: repeat(7,minmax(0,1fr)); gap: 8px; padding: 4px 0 12px; }
    .day-chip { min-width: 0; min-height: 72px; padding: 10px 4px; border-radius: 16px; }
    .day-chip span { font-size: 11px; }
    .day-chip strong { margin-top: 5px; font-size: 18px; }
    .day-chip em { display: block; min-height: 11px; margin-top: 4px; font-size: 10px; font-style: normal; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; }
    .period-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 16px; }
    /* v1.900: Stats top row — period tabs with the single Volume|Sets
       segmented control beside them.
       v1.906: the top row carries the period tabs only — the Weight|Sets
       segmented controls render on the cards (see .stats-metric-toggle).
       Period tabs are squared to the radius-md token like every other
       segmented control. */
    .stats-top-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
    .stats-top-row .period-tabs { margin-bottom: 16px; }
    /* #609: the on-card Weight|Sets toggle sits in the card HEADER,
       right-aligned beside the title (flex head, title left, toggle
       right) — it no longer occupies a row above the card body. */
    .section-head.stats-card-head { align-items: flex-start; }
    .stats-metric-toggle-slot { flex: none; margin-left: auto; }
    .stats-metric-toggle { margin: 0; }
    .period-tab { min-height: 44px; padding: 0 10px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--muted); cursor: pointer; font-size: 12px; font-weight: 750; }
    .period-tab[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
    .heatmap-shell { display: grid; grid-template-columns: minmax(180px,.78fr) minmax(220px,1.22fr); gap: 20px; align-items: center; margin-top: 14px; }
    /* Sasha anatomical body map (restored 2026-09-10). The SVG holds front+back
       side by side in one 760x614 viewBox; heat-N classes paint the regions. */
    .anatomy-map svg { display: block; width: 100%; height: auto; }
    /* The body-map SVG hydrates async; reserve its 760x614 space up front so
       the inject can't shift the page (user 2026-09-12: "crazy weird jump"
       on tab switches — async layout shift is one candidate trigger). */
    .anatomy-map { aspect-ratio: 760 / 614; }
    .anatomy-map .chart-empty { display: flex; align-items: center; justify-content: center; height: 100%; }
    /* (body-map-credit retired 2026-09-10: attribution lives in Settings) */
    /* Per-exercise map on the exercise detail page (user 2026-09-10). */
    .exercise-musclemap { position: relative; }
    /* Phone QA 2026-09-11: similar exercises auto-collapse. */
    .dashboard-card.similar-disclosure { padding: 0; }
    .similar-disclosure > summary { list-style: none; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 18px; cursor: pointer; }
    .similar-disclosure > summary::-webkit-details-marker { display: none; }
    .similar-disclosure > summary strong { display: block; font-size: 17px; font-weight: 800; letter-spacing: -.01em; }
    .similar-disclosure > summary .section-note { display: block; margin-top: 3px; }
    .similar-disclosure[open] > summary { padding-bottom: 8px; }
    .similar-disclosure .similar-grid { padding: 0 18px 18px; }
    .exercise-map-legend { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 8px; color: var(--muted); font-size: 11px; font-weight: 700; }
    .dash-blindspots:empty { display: none; }
    /* Blind-spots section (#13): a horizontal divider, then the label, then the pills. */
    .blindspot-section { margin-top: 14px; }
    .blindspot-rule { border: 0; border-top: 1px solid var(--line); margin: 0 0 10px; }
    .blindspot-label { margin: 0 0 8px; font-size: 12px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
    .blindspot-list { margin-top: 0; }
    .exercise-map-legend span { display: inline-flex; align-items: center; gap: 6px; }
    /* #454 (parked for 2.0): static muscle-name labels in the
       exercise-detail legend. Pills keep the heat-map swatch +
       Primary/Secondary label. */
    .exercise-map-legend .legend-row { flex-wrap: wrap; row-gap: 6px; }
    .legend-kind { font-size: 11px; font-weight: 700; color: var(--muted); }
    .muscle-pill { border: 1px solid var(--accent); background: var(--accent-soft); color: var(--accent); font: 700 12px/1 var(--font-sans); padding: 8px 13px; border-radius: 999px; }
    .heatmap-list { display: grid; gap: 7px; }
    .heatmap-row { display: grid; grid-template-columns: 10px minmax(0,1fr) auto; gap: 8px; align-items: center; font-size: 12px; }
    .heatmap-swatch { width: 10px; height: 10px; border-radius: var(--radius-xs); }
    .heatmap-row span { color: var(--muted); }
    .heatmap-row strong { font-variant-numeric: tabular-nums; }
    .compact-heatmap { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--line); }
    .compact-heatmap .heatmap-shell { grid-template-columns: 150px minmax(0,1fr); gap: 14px; margin-top: 0; }
    .compact-heatmap .heatmap-list { gap: 5px; }
    .compact-heatmap .heatmap-row { font-size: 10px; }
    @container (max-width: 700px) {
      .week-strip { grid-template-columns: repeat(4,minmax(0,1fr)); gap: 7px; }
      .heatmap-shell { grid-template-columns: 1fr; }
      .compact-heatmap .heatmap-shell { grid-template-columns: 140px minmax(0,1fr); }
    }
    @container (max-width: 420px) {
      .compact-heatmap .heatmap-shell { grid-template-columns: 120px minmax(0,1fr); gap: 10px; }
      .compact-heatmap .heatmap-row { grid-template-columns: 8px minmax(0,1fr); }
      .compact-heatmap .heatmap-row strong { display: none; }
    }


    /* Progression engine comparison and per-program controls */

    .exercise-rule-row { padding: 14px 0; border-top: 1px solid var(--line); }
    /* Per-exercise collapsible cards in the exercise picker */
    .exercise-rules-list { display: grid; gap: 10px; margin: 0 0 16px; }
    .exercise-rule-accordion { border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); }
    .exercise-rule-accordion-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 14px; cursor: pointer; list-style: none; }
    .exercise-rule-accordion-head::-webkit-details-marker { display: none; }
    .exercise-rule-accordion-title { display: grid; gap: 2px; min-width: 0; }
    .exercise-rule-accordion-title strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; }
    .exercise-rule-accordion-title small { color: var(--muted); font-size: 11px; font-variant-numeric: tabular-nums; }
    .exercise-rule-accordion[open] .exercise-accordion-chevron { transform: rotate(90deg); }
    .exercise-rule-accordion-body { padding: 0 14px 14px; }
    .exercise-rule-accordion-body .exercise-rule-row { padding: 2px 0 0; border-top: 0; }
    .exercise-rule-head { display: flex; align-items: end; justify-content: space-between; gap: 12px; margin-bottom: 11px; }
    .exercise-rule-head > strong { min-width: 0; font-size: 13px; line-height: 1.25; }
    .set-count-field { width: 72px; flex: 0 0 72px; }
    .exercise-rule-controls { display: grid; grid-template-columns: 96px 72px 72px minmax(190px,1fr); gap: 8px; align-items: end; }
    .rule-field { display: grid; gap: 4px; }
    .rule-field span, .load-control-title { color: var(--muted); font-size: 10px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; }
    /* #316 (user 2026-09-13): Settings numeric fields follow the workout-set
       input styling — 44px, radius-sm, surface-raised, 16px, theme caret,
       accent border + focus ring. Pills, switches, and step controls are
       separate components and untouched. */
    .rule-field input, .rule-field select { width: 100%; min-width: 0; min-height: 44px; height: 44px; margin: 0; padding: 0 9px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface-raised); color: var(--ink); outline: none; font-size: 16px; caret-color: color-mix(in srgb, var(--accent) 45%, transparent); }
    .rule-field input:focus, .rule-field select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--focus-ring); }
    .load-progression { display: grid; gap: 5px; }
    .load-progression-row { display: grid; grid-template-columns: minmax(0,1fr) 76px auto; gap: 6px; align-items: center; }
    .lp-value { position: relative; display: block; min-width: 0; }
    .lp-value .unit { position: absolute; right: 8px; top: 0; bottom: 0; display: inline-flex; align-items: center; font-style: normal; font-size: 11px; color: var(--muted); pointer-events: none; }
    .lp-value input { padding-right: 28px; }
    .load-progression select, .load-progression input { width: 100%; min-width: 0; height: var(--input-h-sm); margin: 0; padding: 0 8px; border: 1px solid var(--line); border-radius: var(--radius-md); background-color: var(--surface); font-size: 16px; }
    .reps-only-toggle { min-height: 44px; padding: 0 10px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--muted); cursor: pointer; font-size: 12px; font-weight: 800; white-space: nowrap; }
    .reps-only-toggle[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
    /* #395: reps-only toggle under the progression summary in Exercise options. */
    .prog-reps-only-row { margin-top: 8px; }
    .prog-reps-only-row .reps-only-toggle { width: 100%; }
    /* #410 (user 2026-09-13): per-exercise dumbbell entry-mode override in
       Exercise options, next to "Increase reps only". */
    .prog-db-entry-row { margin-top: 8px; display: flex; align-items: center; justify-content: space-between; gap: 10px; }
    /* #538: the single-dumbbell row shares the entry-row layout but carries
       its own class — the #495 "hide the Total option" contract asserts no
       .prog-db-entry-row renders while hidden, and this toggle is orthogonal
       to that setting (single-DB exercises still need it in per-mode). */
    .prog-db-single-row { margin-top: 8px; display: flex; align-items: center; justify-content: space-between; gap: 10px; }
    .prog-db-entry-label { font-size: 12px; font-weight: 700; color: var(--muted); }
    /* Phase 5 (v1.904): Single-side row in Exercise options — a switch in
       the Phase 2 token family (pill reserved for switch tracks). */
    .prog-single-side-row { margin-top: 8px; display: flex; align-items: center; justify-content: space-between; gap: 10px; }
    .prog-single-side-row .prog-db-entry-label { display: grid; gap: 2px; }
    .single-side-sub { font-size: 11px; font-weight: 600; color: var(--muted); }
    .single-side-auto { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--accent-hover); background: var(--accent-soft); border: 1px solid var(--accent); border-radius: var(--radius-sm); padding: 1px 5px; margin-left: 4px; }
    .single-side-toggle { position: relative; width: 48px; height: 28px; flex: none; appearance: none; border: none; border-radius: 999px; cursor: pointer; background: var(--line); padding: 0; }
    .single-side-toggle::after { content: ""; position: absolute; top: 3px; left: 3px; width: 22px; height: 22px; border-radius: 50%; background: #fff; box-shadow: 0 1px 4px rgba(0,0,0,.25); transition: left var(--motion-micro) var(--ease-standard); }
    .single-side-toggle[aria-checked="true"] { background: var(--accent); }
    .single-side-toggle[aria-checked="true"]::after { left: 23px; }
    .single-side-note { margin: 8px 0 0; font-size: 12px; font-weight: 600; color: var(--muted); }
    /* Phase 5: L/R badge inside the set-number button (live + builder +
       completed views). */
    .set-side-badge { display: inline-grid; place-items: center; min-width: 16px; height: 16px; margin-left: 3px; padding: 0 3px; border-radius: var(--radius-sm); font-size: 10px; font-weight: 800; vertical-align: super; }
    .set-side-badge.L { background: var(--accent-soft); color: var(--accent-hover); }
    .set-side-badge.R { background: var(--surface-2); color: var(--muted); }
    .set-side-tag { text-transform: uppercase; letter-spacing: .05em; }
    /* Phase 5: predicted starting weight card on the live exercise card. */
    .predict-card { margin-top: 12px; border: 1.5px solid var(--accent); background: var(--accent-soft); border-radius: var(--radius-md); padding: 12px; }
    .predict-card.applied { border-style: dashed; }
    .predict-kicker { font-size: 11px; font-weight: 800; letter-spacing: .07em; text-transform: uppercase; color: var(--accent-hover); }
    .predict-big { font-size: 24px; font-weight: 800; margin: 4px 0 2px; }
    .predict-per { font-size: 14px; font-weight: 700; color: var(--muted); }
    .predict-basis { font-size: 12.5px; font-weight: 600; color: var(--muted); margin: 0; }
    .predict-basis strong { color: var(--ink); }
    .predict-matches { display: grid; gap: 7px; margin: 12px 0 4px; }
    .predict-match { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; background: var(--surface); border: 1.5px solid var(--line); border-radius: var(--radius-sm); padding: 9px 12px; cursor: pointer; font: inherit; }
    .predict-match[aria-pressed="true"] { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); font-weight: 800; }
    .predict-match-name { flex: 1; font-size: 13.5px; }
    .predict-match-est { font-size: 13px; font-weight: 700; color: var(--muted); }
    .predict-actions { display: flex; gap: 8px; margin-top: 12px; }
    .predict-applied-msg { font-size: 13px; font-weight: 700; color: var(--accent-hover); margin: 10px 0 0; }
    .exercise-rule-controls .rule-amrap-hint { grid-column: 1 / -1; }
    .load-progression.is-disabled select, .load-progression.is-disabled input { opacity: .45; pointer-events: none; }
    /* Notched field labels (2026-09-10, user: keep the notch, no visible background).
       The label sits on the field's top border; its background exactly matches the
       local surface via --notch-bg so the border reads as interrupted, no pill. */
    .field { position: relative; display: grid; gap: 0; margin-top: 12px; }
    /* #632 (user 2026-10-18): ONE floating-legend spec for every notched
       field — .field > label, fieldset.field > legend, .rule-field's first
       span, and .workout-focus-legend all render identically: absolute,
       top -7px, left 8px, padding 0 6px, notch background
       var(--notch-bg, var(--bg)), color var(--muted), 12px / 700 /
       letter-spacing .04em (the FOCUS legend's treatment — the workout
       name field was the odd one out, rendered by the 10px META tier).
       The three selectors below are therefore out of the META-tier list. */
    .field > label { position: absolute; top: -7px; left: 8px; z-index: 1; padding: 0 6px; background: var(--notch-bg, var(--bg)); color: var(--muted); font-size: 12px; font-weight: 700; letter-spacing: .04em; }
    .field select { margin-top: 0; }
    .rule-field { position: relative; display: block; margin-top: 12px; }
    /* #206: the notch applies ONLY to the label span (first child). Extra
       spans inside a rule-field (input wrappers, help copy) stay in normal
       flow — the old bare `.rule-field > span` selector yanked every span up
       into the notch and garbled the %1RM rows. */
    .rule-field > span:first-child { position: absolute; top: -7px; left: 8px; z-index: 1; padding: 0 6px; background: var(--notch-bg, var(--bg)); max-width: calc(100% - 16px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--muted); font-size: 12px; font-weight: 700; letter-spacing: .04em; }
    /* Inline unit suffix, e.g. "sec" on the time-step field, "lb"/"%" on increment. */
    .rule-field .unit { position: absolute; right: 12px; top: 0; bottom: 0; display: inline-flex; align-items: center; font-style: normal; font-size: 12px; color: var(--muted); pointer-events: none; }
    .rule-field:has(.unit) input, .rule-field:has(.unit) select { padding-right: 44px; }
    /* Account row → modal (2026-09-10, #37): the signed-in card shows one
       tappable row; details and actions live in the account dialog. */
    .account-row { width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 44px; padding: 6px 0; border: 0; background: transparent; color: inherit; cursor: pointer; text-align: left; font: inherit; }
    .account-info { margin: 0 0 4px; display: grid; gap: 10px; }
    .account-info > div { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
    .account-info dt { color: var(--muted); font-size: 13px; }
    .account-info dd { margin: 0; font-size: 14px; font-weight: 700; text-align: right; }
    /* Time-step preset pills (2026-09-10, user's pick).
       v1.906: every .step-pills button and .rep-preset button is squared
       to var(--radius-md) — the pill radius stays reserved for switch
       tracks and progress bars. */
    .step-pills { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; padding: 8px 2px 2px; }
    .step-pills [data-step] { min-height: 44px; padding: 6px 14px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); font-size: 12px; font-weight: 700; cursor: pointer; }
    .step-pills [data-step][aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
    /* Weight-unit pills share the step-pill look, including the selected state. */
    .step-pills [data-units] { min-height: 44px; padding: 6px 14px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); font-size: 12px; font-weight: 700; cursor: pointer; }
    .step-pills [data-units][aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
    /* Stats default metric pills (user 2026-09-11): same treatment as the unit pills. */
    .step-pills [data-stats-default] { min-height: 44px; padding: 6px 14px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); font-size: 12px; font-weight: 700; cursor: pointer; }
    .step-pills [data-stats-default][aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
    /* Dumbbell weight entry pills (#410, user 2026-09-13): same pill look as
       the other Units rows — the [data-db-entry] buttons shipped without any
       .step-pills rule, so they rendered as unstyled browser buttons with no
       visible selected state. */
    .step-pills [data-db-entry] { min-height: 44px; padding: 6px 14px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); font-size: 12px; font-weight: 700; cursor: pointer; }
    .step-pills [data-db-entry][aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
    /* Effort display pills (RPE/RIR, QA batch 2026-09-22): same pill look as
       the other Settings rows — the [data-effort] buttons shipped without any
       .step-pills rule, so they rendered as unstyled browser buttons with no
       visible selected state (same bug #410 had). */
    .step-pills [data-effort] { min-height: 44px; padding: 6px 14px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); font-size: 12px; font-weight: 700; cursor: pointer; }
    .step-pills [data-effort][aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
    /* #118 (user 2026-09-16): periodization cycle-length pills (Off / 4 / 6 /
       8 / 12 wks) — same pill look as the other Settings rows. */
    .cycle-pills { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; padding: 8px 2px 2px; }
    .cycle-pills .cycle-pill { min-height: 44px; padding: 6px 14px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); font-size: 12px; font-weight: 700; cursor: pointer; }
    .cycle-pills .cycle-pill[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
    /* #89 (user 2026-09-14): program muscle card pills — same pill look as
       the other Units rows. The [data-pm-default] buttons shipped without any
       .step-pills rule, so they rendered as unstyled browser buttons with no
       visible selected state (same bug #410 had). */
    .step-pills [data-pm-default] { min-height: 44px; padding: 6px 14px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); font-size: 12px; font-weight: 700; cursor: pointer; }
    .step-pills [data-pm-default][aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
    /* Settings spacing rhythm (UX audit 2026-09-22): every eyebrow label
       hugs its own control row (8px); the gap before the next label is the
       section break (24px). Supersedes #91, which dated from when two of
       these rows were a single group in the old Units card — the Display
       section split them into four groups and the one-off hug left the
       rhythm ragged and inverted (labels attached to the row above). */
    #settingsView .stats-default-label { margin: 24px 0 8px; }
    #settingsView .settings-section-body > .stats-default-label:first-child { margin-top: 6px; }
    /* Pill rows carry no top padding in Settings — the label's own margin
       sets the label→controls gap, so it can't fight the row's padding. */
    #settingsView .step-pills, #settingsView .cycle-pills { padding-top: 0; }
    /* Sub-heads inside grouped subsections follow the same 8px rhythm. */
    #settingsView .settings-subsection { gap: 8px; }
    /* Preset pill rows (progression mode, RPE trigger, default focus,
       warm-up rungs): 8px under their heading, no trailing margin — the
       parent gap or the help copy's own margin handles what follows. */
    #settingsView .rep-preset-row { margin: 8px 0 0; }
    /* Help copy: fixed 8px top instead of the browser's ~1em default, which
       made pills→help gaps ragged and font-size dependent. */
    #settingsView .field-help { margin: 8px 0 0; }
    /* Notes stacked directly in a section body (Units caption, About rows):
       even 12px rhythm instead of 0–2px pile-ups. */
    #settingsView .settings-section-body > .section-note { margin-top: 12px; }
    #settingsView .settings-section-body > .section-note:first-child { margin-top: 4px; }
    /* Consecutive toggle rows, and the blocks after them, get real section
       breaks instead of 0–4px pile-ups. */
    #settingsView .settings-section-body > .switch-row { margin-top: 14px; }
    #settingsView .settings-section-body > .switch-row:first-child { margin-top: 4px; }
    #settingsView .settings-section-body > .settings-grid { margin-top: 18px; }
    #settingsView .settings-section-body > .settings-grid:first-child { margin-top: 4px; }
    #settingsView .settings-section-body > .settings-subsection { margin-top: 20px; }
    #settingsView .settings-section-body > .settings-subsection:first-child { margin-top: 4px; }
    /* Theme picker (Settings > Appearance, under the Dark mode toggle). */
    .theme-picker { margin-top: 14px; }
    .theme-pills { display: flex; flex-wrap: wrap; gap: 8px; padding: 8px 2px 2px; }
    .theme-pills [data-theme-name] { display: inline-flex; align-items: center; gap: 7px; min-height: 44px; padding: 6px 14px 6px 10px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); font-size: 12px; font-weight: 700; cursor: pointer; }
    .theme-pills [data-theme-name] i { width: 14px; height: 14px; border-radius: 50%; border: 1px solid rgba(0,0,0,.15); }
    .theme-pills [data-theme-name][aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
    /* Theme families (user 2026-09-10): two columns — Cruciferous/Asterid and
       Rosé/Matcha — with a subtle hairline separator between the families. */
    .theme-pills.theme-families { display: flex; align-items: stretch; }
    .theme-family { flex: 1 1 0; display: flex; flex-direction: column; gap: 8px; }
    .theme-family:first-child { padding-right: 12px; }
    .theme-family:last-child { border-left: 1px solid var(--line); padding-left: 12px; }
    .theme-family [data-theme-name] { width: 100%; justify-content: flex-start; }
    .filter-group { position: relative; margin-top: 0; }
    .filter-group > label { position: absolute; top: -7px; left: 8px; z-index: 1; padding: 0 6px; background: var(--notch-bg, var(--bg)); }
    .filter-group > label + select { margin-top: 0; }
    /* Equipment dropdown: custom chevron, notch-consistent field styling. */
    .filter-group select { appearance: none; -webkit-appearance: none; min-height: var(--input-h); padding-right: 36px; border-radius: var(--radius-md);
      background-image: var(--chevron);
      background-repeat: no-repeat; background-position: right 13px center; }
    .filter-panel { --notch-bg: var(--surface); }
    /* Surfaces the notched fields sit on: match the cutout behind each label. */
    .program-setup, .dashboard-card, .custom-dialog { --notch-bg: var(--surface); }
    @container (max-width: 700px) {
      .exercise-rule-controls { grid-template-columns: 1fr 1fr 1fr; }
      .load-progression { grid-column: 1 / -1; }
    }
    @container (max-width: 430px) {
      .exercise-rule-controls { grid-template-columns: 1fr 1fr; }
      .exercise-rule-controls .rule-field:first-child { grid-column: 1 / -1; }
      .load-progression { grid-column: 1 / -1; }
      .load-progression-row { grid-template-columns: minmax(0,1fr) 72px; }
      .reps-only-toggle { grid-column: 1 / -1; }
    }

    .switch-row { grid-column: 1 / -1; display: flex; align-items: center; justify-content: space-between; gap: 14px; padding-top: 4px; }
    .switch-copy { min-width: 0; } /* QA batch (user 2026-09-21, #13): a flex
       child defaults to min-width:auto — long copy could push the row past
       393px on WebKit instead of wrapping. */
    .switch-copy strong, .switch-copy span { display: block; }
    .switch-copy strong { font-size: 12px; }
    .switch-copy span { margin-top: 3px; color: var(--muted); font-size: 10px; line-height: 1.4; }
    /* v1.906: track geometry aligned to the approved single-side
       mockup's switch — 48×28 pill track, 22px knob at a 3px inset, 20px
       of travel so the knob lands at the mockup's 23px on position.
       Colors stay token-driven (accent when on, --switch-track when off,
       surface-raised knob with the same soft shadow) so every theme
       keeps its own palette; the radius stays a full pill. */
    .switch { position: relative; flex: 0 0 auto; width: 48px; height: 28px; padding: 0; border: 0; border-radius: 999px; background: var(--switch-track); cursor: pointer; }
    .switch::before { content: ""; position: absolute; left: 50%; top: 50%; width: 44px; height: 44px; transform: translate(-50%, -50%); }
    .switch::after { content: ''; position: absolute; top: 3px; left: 3px; width: 22px; height: 22px; border-radius: 50%; background: var(--surface-raised); transition: transform var(--motion-micro) var(--ease-standard); }
    .switch[aria-pressed="true"]::after { transform: translateX(20px); }
    /* #420 follow-up (user 2026-09-13): a disabled switch is visibly dimmed
       and non-tappable — used while the %1RM wave default is on, which
       forces Auto Deload off. */
    .switch[disabled] { opacity: .4; cursor: default; }
    /* #405 (user 2026-09-14): when progression is off, the rest of the
       section dims — the off-switch row itself stays interactive. Same
       visual language as the #420 disabled switch. */
    #progressionDefaultsCard.progression-off > .settings-grid,
    #progressionDefaultsCard.progression-off > .switch-row:not(#progressionOffRow),
    #progressionDefaultsCard.progression-off > .undulating-panel,
    section.program-progression.progression-off .progression-disclosure-body > .settings-grid,
    section.program-progression.progression-off .progression-disclosure-body > .switch-row:not(#programProgressionOffRow),
    section.program-progression.progression-off .progression-disclosure-body > .undulating-panel { opacity: .4; pointer-events: none; }
    .progression-summary-copy { flex: 1; min-width: 0; }
    .progression-summary-copy .section-note { display: block; margin: 5px 0 0; }
    .progression-disclosure-body { padding-top: 16px; }
    .suggestion-card { width: 100%; min-width: 0; padding: var(--space-3) var(--space-4); border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface); text-align: left; box-shadow: 0 1px 2px rgba(0,0,0,.03); }
    button.suggestion-card { cursor: pointer; transition: border-color var(--motion-micro) var(--ease-standard), box-shadow var(--motion-micro) var(--ease-standard), background var(--motion-micro) var(--ease-standard); }
    button.suggestion-card:hover { border-color: var(--accent); box-shadow: 0 3px 10px rgba(0,0,0,.06); }
    button.suggestion-card.applied { border-color: var(--accent); background: var(--accent-soft); }
    .suggestion-name { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 8px; color: var(--muted); font-size: 12px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; }
    .suggestion-name span:not(.suggestion-exercise) { flex: none; padding: 4px 11px; border-radius: 999px; background: var(--accent-soft); color: var(--accent); font-size: 10px; font-weight: 800; letter-spacing: .04em; text-transform: none; white-space: nowrap; }
    /* M11: long exercise names ellipsize instead of pushing the basis pill
       off-screen — the name is a real flex child with min-width: 0. */
    .suggestion-name .suggestion-exercise { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .suggestion-change { display: flex; align-items: baseline; gap: 10px; color: var(--muted); font-size: 13px; font-variant-numeric: tabular-nums; }
    .suggestion-change strong { color: var(--ink); font-size: 19px; font-weight: 800; letter-spacing: -.01em; }
    /* #400 (user 2026-09-14): per-set lines on All-sets cards — same
       typographic language as .suggestion-change, one row per moving set. */
    .suggestion-sets { display: flex; flex-direction: column; gap: 6px; }
    .suggestion-set-line { display: flex; align-items: baseline; gap: 8px; color: var(--muted); font-size: 13px; font-variant-numeric: tabular-nums; }
    .suggestion-set-line strong { color: var(--ink); font-size: 16px; font-weight: 800; letter-spacing: -.01em; }
    .suggestion-set-line > span:first-child { color: var(--ink); font-weight: 700; min-width: 44px; }
    .suggestion-set-line.is-hold > span:last-child { color: var(--muted); font-style: italic; }
    /* #406 (user 2026-09-16): the suggestion row inside each exercise card —
       merged with the history line, not a separate banner. Same compact
       language as .last-session-line; the button variant is tappable with
       an accent hover, the applied variant renders quiet. */
    .suggestion-inline { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; width: 100%; margin: 0 0 8px; padding: 9px 11px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface); text-align: left; font-size: 12px; }
    button.suggestion-inline { cursor: pointer; transition: border-color var(--motion-micro) var(--ease-standard), box-shadow var(--motion-micro) var(--ease-standard); }
    button.suggestion-inline:hover { border-color: var(--accent); box-shadow: 0 2px 8px rgba(0,0,0,.05); }
    button.suggestion-inline:active { transform: none; }
    .suggestion-inline.applied { border-color: var(--accent); background: var(--accent-soft); }
    /* #557/#565: informational notice rows — muted, never tappable (a div,
       not a button, so no hover/active treatment applies). */
    .suggestion-inline.notice { background: var(--surface-2); }
    .suggestion-inline.notice .suggestion-inline-kind { background: var(--surface); color: var(--muted); }
    .suggestion-inline-kind { flex: none; padding: 3px 9px; border-radius: 999px; background: var(--accent-soft); color: var(--accent); font-size: 10px; font-weight: 800; letter-spacing: .04em; white-space: nowrap; }
    .suggestion-inline.applied .suggestion-inline-kind { background: transparent; padding-left: 0; }
    .suggestion-inline-target { color: var(--ink); font-size: 15px; font-weight: 800; letter-spacing: -.01em; font-variant-numeric: tabular-nums; }
    .suggestion-inline-basis { flex-basis: 100%; color: var(--muted); font-size: 11px; }
    .progression-banner { margin: 0 0 16px; padding: 14px; border: 1px solid var(--foam); border-radius: var(--radius-md); background: var(--pine-soft); }
    .progression-banner h3 { margin: 0 0 3px; font-size: 14px; }
    .progression-banner p { margin: 0; color: var(--muted); font-size: 10px; line-height: 1.4; }
    .workout-context { display: inline-flex; align-items: center; gap: 6px; margin-bottom: 14px; padding: 6px 9px; border-radius: 999px; background: var(--accent-soft); color: var(--accent); font-size: 11px; font-weight: 750; }
    .program-progression-summary { margin: 18px 0; padding: 14px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface-raised); }
    .program-progression-summary h3 { margin: 0 0 8px; font-size: 14px; }
    /* QA batch (user 2026-09-21): the Active Program progression summary is
       a collapsible card — the engine tags tuck behind a disclosure. */
    .program-progression-disclosure { padding: 0; }
    .program-progression-disclosure > summary { list-style: none; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px; cursor: pointer; }
    .program-progression-disclosure > summary::-webkit-details-marker { display: none; }
    .program-progression-disclosure > summary h3 { margin: 0; }
    .program-progression-disclosure .program-progression-meta { padding: 0 14px 14px; }
    .program-progression-meta { display: flex; flex-wrap: wrap; gap: 6px; }
    @container (max-width: 700px) {
    }
    @container (max-width: 420px) {
    }

    /* One-week calendar, richer workout history, and anatomical body map */
    .calendar-toolbar { display: grid; grid-template-columns: 40px minmax(0,1fr) 40px; gap: 10px; align-items: center; margin: 0 0 8px; }
    .calendar-toolbar > div { min-width: 0; text-align: center; }
    .calendar-toolbar strong, .calendar-toolbar span { display: block; }
    .calendar-toolbar strong { font-size: 13px; }
    .calendar-toolbar span { margin-top: 2px; color: var(--muted); font-size: 10px; }
    .calendar-page { position: relative; width: 44px; height: 44px; padding: 0 0 4px; border: 1px solid var(--line); border-radius: 50%; background: var(--surface); color: var(--ink); cursor: pointer; font-size: 25px; line-height: 1; display: inline-flex; align-items: center; justify-content: center; }
    .calendar-page::before { content: ""; position: absolute; left: 50%; top: 50%; width: 44px; height: 44px; transform: translate(-50%, -50%); }
    /* Optical centering (user 2026-09-10): ‹ › glyphs carry uneven side
       bearings, so the visible chevron sits off-center without a nudge. */
    #previousWeek { transform: translateX(-2px); }
    #nextWeek { transform: translateX(2px); }
    .calendar-page:focus-visible { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); outline: none; }
    @media (hover: hover) {
      .calendar-page:hover { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
    }
    /* Disabled week-pager (user 2026-09-10): grey out the forward control at today. */
    .calendar-page[disabled] { opacity: .35; cursor: default; }
    .calendar-page[disabled]:hover, .calendar-page[disabled]:focus-visible { border-color: var(--line); background: var(--surface); color: var(--ink); outline: none; }
    .week-strip { grid-template-columns: repeat(7,minmax(0,1fr)); touch-action: pan-y; user-select: none; }
    .completed-card { max-width: 900px; }
    .workout-detail-metrics { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 8px; margin: 20px 0; }
    /* Phone QA 2026-09-12: metric cards use the normal white card surface, like the live workout screen. */
    .workout-detail-metric { min-width: 0; padding: 13px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface); }
    .workout-detail-metric strong, .workout-detail-metric span { display: block; }
    .workout-detail-metric strong { font-size: 20px; letter-spacing: -.03em; }
    .workout-detail-metric span { margin-top: 3px; color: var(--muted); font-size: 10px; }
    .workout-muscles, .workout-prs { display: flex; flex-wrap: wrap; gap: 6px; margin: 12px 0 20px; }
    .workout-pr { display: inline-flex; align-items: center; gap: 6px; min-height: 28px; padding: 4px 9px; border-radius: 999px; background: var(--gold-soft); color: var(--warning); font-size: 10px; font-weight: 800; }
    .completed-exercise-details { display: grid; gap: 12px; margin: 22px 0; }
    .completed-exercise-detail { padding: 16px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); }
    .completed-exercise-detail-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
    .completed-exercise-detail-head button { padding: 0; border: 0; background: transparent; color: var(--ink); cursor: pointer; text-align: left; font-weight: 800; }
    .completed-exercise-detail-head .completed-exercise-title { color: var(--ink); font-weight: 800; text-align: left; }
    .completed-exercise-detail-head span { color: var(--muted); font-size: 11px; white-space: nowrap; }
    /* #131: completed sets mirror the live .log-set grid — compact read-only rows. */
    .done-labels, .done-set { display: grid; grid-template-columns: 44px minmax(0,1fr) minmax(0,1fr) minmax(0,1fr); gap: 6px; align-items: center; }
    .done-labels { margin: 4px 0 6px; color: var(--muted); font-size: 10px; font-weight: 750; }
    .done-labels span { text-align: center; white-space: nowrap; }
    .done-set { padding: 10px 0; font-variant-numeric: tabular-nums; }
    .done-set + .done-set { border-top: 1px solid var(--line); }
    .done-num { display: grid; place-items: center; color: var(--muted); font-size: 13px; font-weight: 800; }
    .done-val { text-align: center; font-size: 15px; }
    /* Dumbbell display-mode hotfix: the completed grid's per-dumbbell value
       carries the session's total readout as a sub-line, like the live
       weight field does. */
    .done-val .db-total-readout { display: block; }
    .done-set-tags { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 5px; }
    .completed-note { margin: 12px 0 0; padding-top: 10px; border-top: 1px solid var(--line); color: var(--muted); font-size: 11px; line-height: 1.45; }
    /* #458: the selected-exercises tray grew one rule card per tap with no
       bound, driving the dialog overflow above. Cap it at 32vh with its own
       scroll; the main list below keeps its 48vh scroller. */
    .picker-template-options { margin: 0 0 12px; padding: 12px; border-radius: var(--radius-sm); background: transparent; max-height: 32vh; overflow-y: auto; overscroll-behavior: contain; }
    .picker-template-options strong { display: block; margin-bottom: 8px; font-size: 11px; }
    .template-delete-text { margin-left: auto; border: 0; background: none; min-height: 44px; padding: 8px 4px; color: var(--muted); font-size: 11px; font-weight: 600; cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }
    /* #74: "New template" button below the template list — quiet, full-width, 44px touch target. */
    /* C1 (pixel-peeper PP2): the standard outline treatment — the
       dashed border was the only one in the app. */
    .new-template-button { display: block; width: 100%; min-height: 44px; margin-top: 4px; border: 1px solid var(--line-strong); border-radius: var(--radius-md); background: transparent; color: var(--ink); font-size: 14px; font-weight: 700; cursor: pointer; }
    .new-template-button:active { background: var(--surface-2); }
    /* v0.99v: Archive as a dashed button uses ink text, not the accent. */
    .new-template-button.neutral { color: var(--ink); }
    .new-template-button.neutral:active { background: var(--surface); }
    /* v0.99v: disabled Edit on built-in saved workouts. */
    .secondary-button:disabled, .primary-button:disabled { opacity: .45; cursor: not-allowed; }
    /* v0.99v: saved-workout chooser list inside the add-to-program dialog. */
    .dialog-list { display: grid; gap: 8px; }
    .dialog-list .picker-item { margin-bottom: 0; }
    .anatomy-map { width: 100%; min-height: 280px; }
    .workout-map { margin: 4px 0 10px; }
    .anatomy-map svg { display: block; width: 100%; height: auto; max-height: 360px; }
    .anatomy-map [data-muscle] { stroke: var(--shadow-color); stroke-width: .55; transition: fill var(--motion-micro) var(--ease-standard); }
    /* User 2026-09-14 ("layout shift on open sometimes"): the compact home
       map's pre-hydration reserve must match its hydrated box EXACTLY —
       aspect-ratio 760/614 capped at the same 160px as the injected SVG.
       The old min-height:120px reserve left the host at ~291px on a phone
       until the async SVG fetch landed, then the page jumped ~130px when
       the host collapsed to the 160px-capped SVG. */
    .compact-heatmap .anatomy-map { min-height: 0; aspect-ratio: 760 / 614; max-height: 160px; }
    .compact-heatmap .anatomy-map svg { max-height: 160px; }
    /* #182: the min-height is only a pre-hydration placeholder reserve
       (anti layout-shift); once the SVG is injected the host hugs it, so
       no dead space sits between the heatmaps and the legend list.
       User 2026-09-13: the 760/614 aspect-ratio reserve must release too —
       on the compact home map the ratio box stayed ~290px tall under the
       160px-capped SVG, leaving dead space below the figures. The JS now
       sets data-hydrated on inject (it never did before, so this rule was
       dead code). */
    .anatomy-map[data-hydrated] { min-height: 0; aspect-ratio: auto; }
    @container (max-width: 700px) {
      .week-strip { grid-template-columns: repeat(7,minmax(0,1fr)); gap: 4px; }
      .day-chip { min-height: 68px; padding: 8px 2px; border-radius: 13px; }
      .day-chip span { font-size: 10px; }
      .day-chip strong { font-size: 16px; }
      .heatmap-shell, .compact-heatmap .heatmap-shell { grid-template-columns: 1fr; }
      .workout-detail-metrics { grid-template-columns: repeat(3,minmax(0,1fr)); }
      .program-workout { grid-template-columns: 30px minmax(0,1fr); }
    }
    /* #67: calm ghost button — the card already has plenty of chrome. */
    .add-set { display: flex; align-items: center; justify-content: center; width: 100%; min-height: var(--btn-h-sm); margin: 12px 0 0; padding: 0 13px; border: 1px solid var(--line); border-radius: var(--radius-md); background: transparent; color: var(--accent); cursor: pointer; font-size: 13px; font-weight: 750; }
    .add-set:active { border-color: var(--accent); background: var(--accent-soft); }
    .add-set:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
    /* #185 (user 2026-09-13): the Warm-up button — same ghost treatment as
       + Add set, plain text, no emoji. Remove warm-ups is a quiet text
       action; both share the set-utility-row. */
    .add-warmup { display: flex; align-items: center; justify-content: center; width: 100%; min-height: 44px; margin: 12px 0 0; padding: 0 13px; border: 1px dashed var(--line-strong); border-radius: var(--radius-md); background: transparent; color: var(--ink); cursor: pointer; font-size: 13px; font-weight: 750; }
    .add-warmup:active { border-color: var(--accent); background: var(--accent-soft); }
    .add-warmup:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
    /* #185 (user 2026-09-13): the live-workout Warm-up control lives in the
       card header, left of the info icon — a small dashed rectangle, not a
       full-width row. #307 (user 2026-09-13): fixed 32px height +
       inline-flex centering; v1.906: the dashed shape is squared to
       radius-md and stays centered in the header row.
       The 44px tap target rides on ::before like the info button's. */
    .warmup-corner { position: relative; flex: none; align-self: center; height: 32px; padding: 0 12px; display: inline-flex; align-items: center; border: 1px dashed var(--line-strong); border-radius: var(--radius-md); background: transparent; color: var(--ink); font-size: 12px; font-weight: 750; line-height: 1; white-space: nowrap; cursor: pointer; }
    .warmup-corner::before { content: ""; position: absolute; left: 50%; top: 50%; min-width: 44px; width: 100%; height: 44px; transform: translate(-50%, -50%); }
    .warmup-corner:active { border-color: var(--accent); background: var(--accent-soft); }
    .warmup-corner:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
    /* (set-count-badge retired 2026-09-10: the accordion summary already carries the set count) */
    .muscle-analysis-grid { display: grid; grid-template-columns: minmax(0,1.15fr) minmax(250px,.85fr); gap: 12px; margin-top: 12px; }
    .analysis-panel { padding: var(--card-pad); border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface); }
    .analysis-panel h2 { margin: 0 0 4px; font-size: 17px; }
    .analysis-panel > p { margin: 0 0 16px; color: var(--muted); font-size: 11px; line-height: 1.45; }
    .muscle-volume-list, .muscle-trend-list { display: grid; gap: 11px; }
    .muscle-volume-row { display: grid; grid-template-columns: minmax(82px,.65fr) minmax(100px,1fr) auto; gap: 9px; align-items: center; font-size: 11px; }
    .analysis-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-transform: capitalize; }
    .analysis-track { height: 7px; border-radius: 999px; background: var(--surface-2); overflow: hidden; }
    .analysis-fill { display: block; height: 100%; border-radius: inherit; background: var(--accent); }
    .analysis-value { color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
    .trend-row { display: grid; grid-template-columns: minmax(0,1fr) auto; gap: 12px; align-items: center; padding: 9px 0; border-top: 1px solid var(--line); }
    .trend-row:first-child { padding-top: 0; border-top: 0; }
    .trend-row strong, .trend-row span { display: block; }
    .trend-row strong { font-size: 12px; text-transform: capitalize; }
    .trend-row span { margin-top: 2px; color: var(--muted); font-size: 10px; }
    .trend-change { padding: 4px 7px; border-radius: 999px; background: var(--surface-2); color: var(--muted); font-size: 10px; font-weight: 800; }
    .trend-change.up { background: var(--accent-soft); color: var(--accent); }
    .trend-change.down { background: var(--rose-soft); color: var(--warning); }
    @container (max-width: 700px) { .muscle-analysis-grid { grid-template-columns: 1fr; } }
    .tracking-segment { display: inline-flex; padding: 3px; gap: 3px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); }
    .tracking-segment button { min-height: 36px; padding: 0 12px; border: 0; border-radius: var(--radius-md); background: transparent; color: var(--muted); cursor: pointer; font-size: 12px; font-weight: 750; box-shadow: none; }
    .tracking-segment button[aria-pressed="true"] { background: var(--accent-soft); color: var(--accent); box-shadow: none; }
    /* #498: distance-tracked exercises show a static metric label where the
       reps/seconds toggle would be. */
    .tracking-segment .tracking-static { display: inline-flex; align-items: center; min-height: 36px; padding: 0 12px; font-size: 12px; font-weight: 750; color: var(--accent); }
    /* #64: source tabs in the add-to-program dialog — full-width row, spaced
       from the list below. */
    .add-to-program-tabs { display: flex; margin: 2px 0 10px; }
    .add-to-program-tabs button { flex: 1; }
    .suggestion-card.applied .suggestion-change strong::after { content: ' · applied'; color: var(--accent); font-size: 10px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; }

    /* Exercise tags and reusable program templates */
    .exercise-tag-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 9px; }
    /* #631 (user 2026-10-18): the 10px/23px chips read as specks — enlarged
       to a readable 13px on a 28px+ tap-friendly box. (.set-tag-chip is a
       separate class and keeps its own size.) */
    .exercise-tag-chip { display: inline-flex; align-items: center; min-height: 28px; padding: 6px 10px; border-radius: 999px; background: var(--surface-2); color: var(--ink); font-size: 13px; font-weight: 750; }
    .exercise-tag-chip.preset { background: var(--accent-soft); color: var(--accent); }
    .exercise-tag-button { min-height: 44px; padding: 0 8px; border: 1px dashed var(--line-strong); border-radius: var(--radius-md); background: transparent; color: var(--muted); cursor: pointer; font-size: 12px; font-weight: 750; }
    .exercise-tag-button:hover, .exercise-tag-button:focus-visible { border-color: var(--accent); color: var(--accent); outline: none; }
    .exercise-tags-builder { margin-top: 11px; padding-top: 10px; border-top: 1px solid var(--line); }
    .exercise-tags-builder .exercise-tag-row { margin-top: 0; }
    .archive-row-wrap { border-top: 1px solid var(--line); }
    .archive-row-wrap .archive-row { border-top: 0; }
    .archive-history { padding: 0 0 14px; }
    .archive-history summary { color: var(--accent); cursor: pointer; font-size: 11px; font-weight: 800; }
    .archive-history-list { display: grid; gap: 6px; margin-top: 9px; }
    .archive-workout { width: 100%; min-height: 44px; padding: 8px 10px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface); display: flex; align-items: center; justify-content: space-between; gap: 10px; text-align: left; cursor: pointer; }
    .archive-workout strong { font-size: 12px; }
    .archive-workout span { color: var(--muted); font-size: 10px; }
    @container (max-width: 520px) {
        .workout-detail-metric { padding: 10px 8px; }
      .workout-detail-metric strong { font-size: 17px; }
      .calendar-toolbar span { display: none; }
    }
    /* Unified builder, lighter program editing, and compact logging controls */
    /* A4 (pixel-peeper PP1): the start options are divider-separated rows
       inside the single hero card — no card-in-card nesting.
       QA batch (user 2026-09-22): the container's top border stacked with the
       program card's own border + tinted wrapper and read as a stray double
       line — the rows carry their own bottom dividers, so the top border
       is dropped. */
    .workout-start-options { display: grid; grid-template-columns: 1fr; gap: 0; margin-top: 18px; }
    .start-option { width: 100%; min-height: 0; padding: 14px 2px; border: 0; border-bottom: 1px solid var(--line); border-radius: 0; background: transparent; box-shadow: none; text-align: left; cursor: pointer; }
    .start-option strong { display: block; margin-bottom: 2px; font-size: 15px; }
    .start-option span { color: var(--muted); font-size: 12px; line-height: 1.4; }
    .start-option.primary { border: 0; border-bottom: 1px solid var(--line); background: transparent; }
    .start-option.primary strong { color: var(--accent); }
    .built-in-label { display: inline-block; margin-left: 6px; padding: 1px 7px; border-radius: 999px; background: var(--accent-soft); color: var(--accent); font-size: 10px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; vertical-align: middle; }
    .advanced-options summary::-webkit-details-marker { display: none; }
    .advanced-options summary::after { content: '›'; color: var(--muted); font-size: 18px; font-weight: 500; line-height: 1; transition: transform var(--motion-micro) var(--ease-standard); }
    .advanced-options[open] > summary::after { transform: rotate(90deg); }
    .advanced-options { margin-top: 10px; border-top: 1px solid var(--line); }
    .advanced-options summary { display: flex; align-items: center; justify-content: space-between; min-height: 44px; color: var(--muted); cursor: pointer; font-size: 11px; font-weight: 800; }
    .workout-focus-open .field-help { margin: 8px 0 0; }
    .workout-focus-label { display: block; margin-bottom: 8px; color: var(--muted); font-size: 11px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; }
    .advanced-options-body { display: grid; gap: 10px; padding: 2px 0 8px; }
    /* Progression summary under Exercise options (user 2026-09-11): shows the
       exercise's progression setup so the suggestion basis is visible. */
    .prog-summary { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; margin-top: 10px; padding: 8px 10px; border-radius: 8px; background: var(--surface-2); font-size: 12px; }
    /* #617 (v1.908): progression is a flat section inside Exercise options —
       a "Progression" heading row with the controls inline below it. The
       old collapsible card's summary button (and its chevron) is gone. */
    .progression-section { margin-top: 10px; border: 1px solid var(--line); border-radius: 10px; background: var(--surface-2); }
    .progression-section-head { display: flex; align-items: center; gap: 8px; padding: 10px 12px; font-size: 13px; }
    .prog-card-title { font-weight: 700; }
    .progression-section .prog-scheme { font-weight: 800; color: var(--ink); }
    .progression-section .prog-target { font-weight: 700; color: var(--ink); }
    .progression-card-body { padding: 0 12px 12px; }
    .progression-card-body .prog-summary { margin-top: 0; background: var(--surface); }
    .prog-scheme { font-weight: 800; color: var(--ink); }
    .prog-target { font-weight: 700; color: var(--ink); }
    .prog-detail { color: var(--muted); }
    .prog-basis { flex-basis: 100%; color: var(--muted); font-size: 11px; line-height: 1.4; }
    /* Hairline above Remove exercise (user 2026-09-11): separates the
       destructive action from the rest of Exercise options. */
    .remove-exercise-separator { border-top: 1px solid var(--line); margin: 12px 0 4px; }
    /* Similar exercises collapse on the exercise detail (user 2026-09-10). */
    .set-utility-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
    /* #332 (user 2026-09-13): the emptied-exercise state — quiet note plus
       an explicit Remove exercise, sitting where the sets were. */
    .empty-exercise-state { padding: 14px 4px; display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }
    .empty-exercise-state .section-note { margin: 0; }
    /* Circular add/reorder toolbar buttons are plain (non-green), SVG-centered
       (user 2026-09-12). */
    .rep-preset-row { display: flex; flex-wrap: wrap; gap: 7px; margin: 9px 0 12px; }
    .rep-preset { min-height: 44px; padding: 0 11px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); cursor: pointer; font-size: 12px; font-weight: 750; }
    .rep-preset[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
    /* #97 (user 2026-09-11): workout-focus rows use square-ish option boxes
       like the RPE squares, not pills — compact, still 44px tall targets. */
    .workout-focus-open .rep-preset { border-radius: var(--radius-md); padding: 0 10px; }
    /* #97 (user 2026-09-11): RPE threshold uses square option boxes like the
       onboarding prototype, not pills. Compact squares with green border +
       tint when selected. */
    #settingsRpePills { gap: 8px; }
    #settingsRpePills .rep-preset, #programRpePills .rep-preset { border-radius: var(--radius-md); width: 44px; height: 44px; min-width: 0; min-height: 0; padding: 0; font-size: 15px; font-weight: 700; }
    /* Flat progression section inside program setup (2026-09-10, #45): no
       nested card — same visual language as Settings → Progression defaults,
       separated by a top rule. */
    .program-progression { margin: 24px 0 0; padding: 22px 0 0; border-top: 1px solid var(--line); }
    .program-progression .settings-head { margin-bottom: 16px; }
    .program-progression .settings-title-row .program-progression-title { margin: 0; font-size: 19px; font-weight: 800; letter-spacing: -.02em; }
    .program-progression .settings-grid { grid-template-columns: 1fr; gap: 16px; margin-bottom: 6px; }
    .program-progression .switch-row { margin-top: 12px; padding: 18px 0 2px; border-top: 1px solid var(--line); }
    .undulating-panel { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line); }
    .week-range-list { display: grid; gap: 12px; margin-top: 10px; }
    .week-range-row strong { display: block; margin-bottom: 6px; font-size: 11px; }
    .week-range-pills { display: flex; flex-wrap: wrap; gap: 6px; }
    .week-range-pills .rep-preset { min-height: 44px; padding: 0 10px; font-size: 12px; }
    /* %1RM per-week % wave (#54, v1.001; #255 restyle 2026-09-13): standard
       input components — bordered inputs with focus rings, aligned rows. */
    .week-pct-list { display: grid; gap: 12px; margin-top: 10px; }
    .week-pct-row { display: grid; grid-template-columns: minmax(0,1fr) auto auto; align-items: center; gap: 10px; }
    .week-pct-id { display: grid; gap: 1px; min-width: 0; }
    .week-pct-id strong { font-size: 13px; }
    .week-pct-range { font-size: 12px; color: var(--muted); line-height: 1.4; overflow-wrap: anywhere; }
    .week-pct-input { display: inline-flex; align-items: center; gap: 6px; }
    .week-pct-input input { width: 76px; min-height: 44px; height: 44px; margin: 0; padding: 0 9px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface-raised); color: var(--ink); outline: none; font-size: 16px; text-align: center; }
    .week-pct-input input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--focus-ring); }
    .week-pct-input .unit { font-size: 12px; color: var(--muted); font-style: normal; }
    /* %1RM per-exercise inputs in exercise options (#54, v1.001). */
    .prog-onerm-fields { display: flex; flex-wrap: wrap; gap: 10px 14px; width: 100%; margin-top: 6px; }
    .prog-onerm-field { display: flex; flex-direction: column; gap: 4px; font-size: 11px; color: var(--muted); }
    .prog-onerm-input { display: inline-flex; align-items: center; gap: 4px; }
    .prog-onerm-input input { width: 84px; min-height: 44px; font-size: 16px; text-align: center; }
    .prog-onerm-input .unit { font-size: 12px; color: var(--muted); }
    .program-cover-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 18px; }
    /* Compact Edit program button (user 2026-09-11): small pill, not a big CTA. */
    .program-cover-actions .secondary-button { min-height: var(--btn-h-sm); padding: 0 16px; border-radius: var(--radius-md); font-size: 13px; }
    .program-muscles { margin-top: 18px; }
    .program-muscles h3 { margin: 0 0 8px; font-size: 13px; }
    .program-muscle-bars { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 7px; }
    .program-muscle-bar { display: grid; grid-template-columns: minmax(70px,.7fr) minmax(80px,1fr); gap: 8px; align-items: center; font-size: 10px; }
    .program-muscle-bar i { height: 6px; border-radius: 999px; background: var(--accent-soft); overflow: hidden; }
    .program-muscle-bar i::after { content:''; display:block; width:var(--fill); height:100%; background:var(--accent); border-radius:inherit; }
    @container (max-width: 640px) {
      .workout-start-options, .program-muscle-bars { grid-template-columns: 1fr; }
      .week-range-list { grid-template-columns: 1fr; }
    }

    /* ===== refined theme overrides ===== */
    html, body { background: var(--bg); color: var(--ink); }
    h1, h2, h3,
    .stat-value, .workout-detail-metric strong,
    .completed-card h2, .program-cover-head h2, .start-option strong {
      font-family: var(--font-sans);
      color: var(--ink-strong);
    }
    h1 { letter-spacing: -.045em; }
    h2, h3 { letter-spacing: -.025em; }
    /* META tier (v1.007, user 2026-09-12): one of exactly two sanctioned
       micro-label sizes — 10px/800/uppercase metadata labels. The SECTION
       tier is 12px/800/.08em/uppercase (.upcoming-kicker, .chart-kicker,
       .program-cover-kicker, .continue-kicker, .blindspot-label,
       .suggestion-name, .picker-section-label). */
    .filter-group label, .filter-label,
    .stat-label, .log-labels,
    .load-control-title {
      color: var(--muted);
      font-size: 10px;
      font-weight: 800;
      letter-spacing: .11em;
      text-transform: uppercase;
      font-variant-caps: all-small-caps;
    }
    button, input, textarea, select { color: var(--ink); }
    button:focus-visible, input:focus-visible, textarea:focus-visible, select:focus-visible,
    .form-pill:focus-visible, .day-chip:focus-visible {
      outline: 3px solid var(--focus-ring);
      outline-offset: 2px;
    }
    /* A2 (pixel-peeper PP1): segmented pills draw their focus ring INSIDE
       (inset box-shadow) instead of the outset outline above — tapping a
       pill never changes its outer dimensions. */
    .step-pills button:focus-visible, .cycle-pills button:focus-visible,
    .rep-preset:focus-visible, .tracking-segment button:focus-visible,
    .mini-segmented button:focus-visible, .period-tab:focus-visible,
    .theme-pills button:focus-visible, .muscle-option:focus-visible,
    .reps-only-toggle:focus-visible, .top-bar-btn:focus-visible {
      outline: none;
      box-shadow: inset 0 0 0 2px var(--accent);
    }
    /* Phase 2 (v1.901) supersedes A2 for the SELECTED state: selected =
       the app's soft wash — accent-soft fill, accent border, accent text.
       Never the 2px inset ring, never a shadow. (Keyboard :focus-visible
       above keeps its inset ring — that is focus, not selection.) */
    .step-pills button[aria-pressed="true"], .cycle-pills button[aria-pressed="true"],
    .tracking-segment button[aria-pressed="true"], .mini-segmented button[aria-pressed="true"],
    .period-tab[aria-pressed="true"], .theme-pills button[aria-pressed="true"],
    .rep-preset[aria-pressed="true"], .muscle-option[aria-pressed="true"],
    .reps-only-toggle[aria-pressed="true"] {
      background: var(--accent-soft);
      border-color: var(--accent);
      color: var(--accent);
      box-shadow: none;
      /* #607 (v1.907): prod's active states read bolder — the selected
         option carries 800 against the 750 base weight. */
      font-weight: 800;
    }
    .dashboard-card, .stats-panel, .section, .workout-start, .workout-exercise,
    .program-setup, .program-cover, .completed-card, .treatment-panel, .analysis-panel,
    .history-session, .exercise-card, .chart-card {
      border-color: var(--line);
      border-radius: var(--radius-lg);
      background: var(--surface);
      box-shadow: var(--shadow-soft);
    }
    .dashboard-card, .stats-panel, .workout-exercise, .completed-card, .analysis-panel { padding: var(--card-pad); }
    .exercise-card { border-radius: var(--radius-lg); }
    .exercise-card:hover { border-color: var(--line-strong); box-shadow: var(--shadow-soft); }
    .add-exercise{
      border: 1px solid var(--accent);
      border-radius: var(--radius-md);
      background: var(--accent);
      color: var(--on-accent);
      box-shadow: none;
    }
    .add-exercise:hover{ background: var(--accent-hover); }
    /* Phase 2 (v1.901): the secondary/tool family is squared radius-md.
       .calendar-page is split out — its prod circular pill treatment stays. */
    .small-button, .filter-button,
    .back, .add-set {
      border-color: var(--line-strong);
      border-radius: var(--radius-md);
      background: var(--surface-raised);
      color: var(--ink-strong);
    }
    .calendar-page {
      border-color: var(--line-strong);
      border-radius: 999px;
      background: var(--surface-raised);
      color: var(--ink-strong);
    }
    .small-button:hover, .filter-button:hover,
    .back:hover, .add-set:hover {
      /* Neutral hover stays neutral — never flashes red (#44). iOS Safari keeps
         :hover stuck after a tap, so a red hover reads as a broken state (#26). */
      filter: brightness(.96);
    }
    /* Destructive hovers stay in the red family — never flip to accent green (#44). */
    .primary-button.danger-button:hover { background: var(--danger); filter: brightness(.85); }
    .secondary-button.danger-button:hover, .small-button.danger:hover {
      border-color: var(--danger);
      background: var(--danger-soft);
      filter: none;
    }
    .search, select, .field input, .field textarea,
    .picker-search, .tag-editor input, .log-input, .exercise-note textarea,
    .rule-field input, .rule-field select, .load-progression input, .load-progression select, .week-range-row select {
      border-color: var(--line);
      border-radius: var(--radius-md);
      background: var(--surface-raised);
      color: var(--ink);
    }
    .search:focus, .field input:focus, .field textarea:focus, .log-input:focus,
    .exercise-note textarea:focus, .picker-search:focus, select:focus {
      border-color: var(--accent);
      box-shadow: 0 0 0 3px var(--focus-ring);
      outline: none;
    }
    .tag, .exercise-tag-chip, .set-tag-chip,
    .workout-pr {
      border-radius: 999px;
      background: var(--surface-2);
      color: var(--muted);
    }
    /* #682 (user 2026-10-19): the completed view's PR marks get the share
       cards' gold treatment, not the tag group's grey pill — the group
       rule above contributes only the 999px radius; the gold colors,
       border, and roomier spacing are re-asserted here (this rule follows
       the group in the cascade, so it wins). Gold is reserved for PRs. */
    .workout-pr {
      background: var(--gold-soft);
      color: var(--warning);
      border: 1px solid var(--gold);
      min-height: 34px;
      padding: 7px 13px;
      gap: 8px;
      font-size: 12px;
    }
    .exercise-tag-chip.preset, .set-tag-chip {
      border-color: var(--accent);
      background: var(--accent-soft);
      color: var(--accent);
    }
    /* Design paradigm (user 2026-09-10, locked): emphasis treatments use ONE
       theme accent — var(--accent) for text/border, var(--accent-soft) for the
       fill. Never mix a second hue token (pine/rose) or a separate ink token
       into the same element: pine border + mauve text read as broken. The old
       --accent-ink token is retired. */
    /* Note: .period-tab[aria-pressed="true"] is deliberately NOT in the list
       above — its active state is the single-accent treatment at the
       .period-tab rule, so the border and text never come from two different
       tokens (user 2026-09-10: teal border + pink text looked broken). */
    /* Note: .switch is deliberately excluded here — its off-state track uses
       --switch-track (visible on light themes), set in the base .switch rule. */
    .form-pill, .muscle-option, .reps-only-toggle,
    .exercise-tag-button{
      border-color: var(--line-strong);
      background: var(--surface-raised);
      color: var(--muted);
    }
    .form-pill[aria-pressed="true"], .muscle-option[aria-pressed="true"],
    .reps-only-toggle[aria-pressed="true"]{
      border-color: var(--accent);
      background: var(--accent-soft);
      color: var(--accent);
      font-weight: 800; /* #607 (v1.907): prod-strength active bolding */
    }
    .switch[aria-pressed="true"] { background: var(--accent); }
    .switch::after { background: var(--surface-raised); box-shadow: 0 1px 4px var(--shadow-color); }
    /* A4 (pixel-peeper PP1): the divider-row treatment holds here too —
       no card styling is reintroduced. */
    .workout-start-options { gap: 0; }
    .start-option { border: 0; border-bottom: 1px solid var(--line); border-radius: 0; background: transparent; box-shadow: none; }
    .start-option.primary { border: 0; border-bottom: 1px solid var(--line); background: transparent; }
    .start-option:hover { border-bottom-color: var(--accent); }
    /* Set rows: full-bleed inline ledger rows — no boxes, no side borders (user 2026-09-10, UX consult). */
    .log-set { padding: 12px 0; border: 0; background: transparent; }
    .log-set + .log-set { border-top: 1px solid var(--line); }
    .log-set-number { border-radius: var(--radius-md); background: transparent; }
    .log-set-number.has-tags { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
    .log-set.is-complete { background: transparent; }
    /* #93 (user 2026-09-11): swipeable rows need an opaque background to hide
       the delete rail underneath — the transparent above would let the red
       show through on checked sets. */
    .set-swipe .log-set.is-complete { background: var(--surface); }
    .log-set.is-complete .log-input { border-color: var(--line); background: var(--surface-raised); }
    /* #161: frozen (completed-set) inputs read as locked — muted ink, default
       cursor, no layout shift. The real `readonly` attribute does the editing
       block; this is only the visual read. The disabled tag button keeps its
       normal look (no faded disabled wash) so the row doesn't read broken. */
    .log-set.is-complete .log-input[readonly] { color: var(--muted); cursor: default; }
    .log-set.is-complete .log-set-number:disabled { cursor: default; opacity: 1; }
    /* #86 mirror (user 2026-09-11): swiping open a rail on an already-completed
       set — dim the checkmark so the delete action reads as foreground. */
    .swipe-item.is-open .log-set.is-complete .complete-set { opacity: .35; }
    .complete-set { border-color: var(--line-strong); }
    .selected-set-tags { padding-inline: 0; }
    .advanced-options { border-color: var(--line); border-radius: var(--radius-sm); background: var(--surface-2); }
    .advanced-options summary { color: var(--ink-strong); }
    .suggestion-card, button.suggestion-card {
      min-height: 62px;
      border-color: var(--line);
      border-radius: var(--radius-lg);
      background: var(--surface-raised);
      box-shadow: var(--shadow-soft);
    }
    button.suggestion-card.applied { border-color: var(--accent); background: var(--accent-soft); }
    @media (hover: hover) {
      button.suggestion-card:hover { border-color: var(--accent); background: var(--accent-soft); }
    }
    .suggestion-card.applied .suggestion-change strong::after { color: var(--accent); }
    .progression-banner { border-color: var(--line); border-radius: var(--radius-md); background: var(--surface-2); }
    .program-cover { border-color: var(--line-strong); }
    .program-body { background: var(--surface); }
    .week-segment { background: var(--surface-2); }
    .week-segment.past { background: var(--foam); }
    .week-segment.current { background: var(--gold); box-shadow: 0 0 0 2px var(--gold-soft); }
    /* Scheduled deload week (#54, v1.001): hatched marker on the cover pattern. */
    .week-segment.deload { background-image: repeating-linear-gradient(45deg, transparent, transparent 2px, rgba(0,0,0,.25) 2px, rgba(0,0,0,.25) 4px); }
    .program-workout, .archive-workout { border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface-raised); }
    /* #132: archived rows are flat separator rows, not nested cards. */
    .archive-row { padding: 13px 2px; }
    .archive-meta { text-wrap: pretty; }
    .archive-restore { flex: none; border: 1px solid var(--line); border-radius: var(--radius-md); background: transparent; color: var(--ink); font-size: 12px; font-weight: 700; padding: 8px 14px; min-height: 36px; cursor: pointer; }
    /* #461 (user 2026-09-15): archived rows carry Restore + Delete side by
       side; Delete reads danger-colored but keeps the quiet shape. */
    .archive-actions { display: flex; gap: 8px; flex: none; }
    .archive-delete { color: var(--danger); }
    .program-muscle-bar i { background: var(--surface-2); }
    .program-muscle-bar i::after, .analysis-fill { background: var(--foam); }
    /* Phone QA 2026-09-12: metric cards use the normal white card surface. */
    .workout-detail-metric { border-color: var(--line); border-radius: var(--radius-md); background: var(--surface); }
    .custom-dialog { border-color: var(--line-strong); border-radius: var(--radius-lg); background: var(--surface); box-shadow: var(--shadow); }
    .custom-dialog::backdrop { background: var(--backdrop); }
    .dialog-close { border-color: var(--line); background: var(--surface); color: var(--muted); }
    .picker-item { border-color: var(--line); border-radius: var(--radius-md); background: var(--surface-raised); }
    .picker-item[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); font-weight: 800; }
    @media (hover: hover) {
      .picker-item:hover { border-color: var(--accent); background: var(--accent-soft); }
    }
    .picker-footer { border-color: var(--line); background: var(--surface); }
    /* #99 H1: never re-add a `background` shorthand here — it wipes the opaque
       background-color + gradient layer from the base rule, defeating the iOS 26
       "black bars" fix (Safari samples fixed bars, ignores theme-color). */
    .bottom-nav {
      border-top-color: var(--line-strong);
    }
    .nav-item { border-radius: 18px; color: var(--muted); }
    .nav-item.active { background: var(--accent-soft); color: var(--accent); font-weight: 700; }
    .day-chip { border-color: var(--line); border-radius: var(--radius-lg); background: var(--surface-raised); }
    .day-chip.today { border-color: var(--accent); background: transparent; }
    .day-chip.selected { border-color: var(--accent); background: var(--accent); color: var(--on-accent); box-shadow: none; }
    .day-chip.selected span { color: var(--on-accent); opacity: .72; }
    .day-chip.selected strong::after, .day-chip.selected em i { background: var(--on-accent); }
    .day-chip.has-workout strong::after { background: var(--accent); }
    @media (hover: hover) {
    }
    @container (max-width: 640px) {
      .dashboard-card, .stats-panel, .workout-exercise, .completed-card, .analysis-panel { padding: 16px; }
    }

    /* Training home, theme, accessible set detail, and lightweight tools */
    /* #120 (user 2026-09-22): was 190px — ~135px of dead space above the
       tab bar. Nav clearance is 82px; 112px leaves ~30px breathing room. */
    .shell { padding-bottom: calc(112px + env(safe-area-inset-bottom)); }
    .training-home { display: grid; gap: 28px; }
    .training-hero { position: relative; overflow: hidden; padding: clamp(22px,5vw,42px); border: 1px solid var(--line-strong); border-radius: var(--radius-lg); background: var(--surface); box-shadow: var(--shadow-soft); }
    /* A1 (pixel-peeper PP2): empty-state headings share one scale — 22px
       semibold, matching .empty-state-title. */
    .training-hero h2 { margin: 5px 0 8px; font: 700 22px/1.25 var(--font-sans); }
    .training-hero p { max-width: 560px; color: var(--muted); }
    .program-next-wrap { position: relative; z-index: 1; margin: -10px -10px 24px; padding: 10px; border-radius: var(--radius-lg); background: var(--pine-soft); }
    .program-next-main { width: 100%; display: grid; grid-template-columns: minmax(0,1fr) auto; align-items: center; gap: var(--space-4); padding: var(--card-pad); border: 1px solid var(--accent); border-radius: var(--radius-lg); background: var(--surface); color: var(--ink); text-align: left; cursor: pointer; box-shadow: var(--shadow-soft); }
    .program-next-main:hover { border-color: var(--accent-hover); }
    .program-next-kicker { display: block; margin-bottom: 5px; color: var(--accent); font-size: 10px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; }
    .program-next-main strong { display: block; font-size: 18px; line-height: 1.25; }
    .program-next-main small { display: block; margin-top: 5px; color: var(--muted); font-size: 11px; line-height: 1.35; }
    /* #254 (user 2026-09-12): under Rosé the foam/pine tokens read blue — the
       Continue-program card's border/glow must follow the theme accent
       (pink), not the blue foam. Scoped to Rosé; other themes keep their
       own foam/pine treatment. */
    [data-theme="rosepine"] .program-next-main { border-color: var(--accent); }
    [data-theme="rosepine"] .program-next-main:hover { border-color: var(--accent); }
    /* Browser QA 2026-09-12: the wrap's halo also read blue — it must follow
       the accent too, as a soft wash. */
    [data-theme="rosepine"] .program-next-wrap { background: color-mix(in srgb, var(--accent) 16%, transparent); }
    /* User 2026-09-12 (phone QA): the card's shadow still read as a blue
       glow — the lavender-gray --shadow-soft must go accent-tinted too. */
    [data-theme="rosepine"] .program-next-main { box-shadow: 0 6px 18px color-mix(in srgb, var(--accent) 30%, transparent); }
    /* ===== #196: no-program workout home (user 2026-09-12) =====
       The blank-workout card moves into the hero slot; the Create-a-program
       card keeps the › affordance of the card it replaces. */
    #programStartSuggestion .start-option { width: 100%; }
    /* #377 (user 2026-09-13): all start options live under "Start training" —
       the program card spans the full options grid and keeps its highlight;
       its old above-the-kicker negative margins don't apply in the grid. */
    .workout-start-options #programStartSuggestion { grid-column: 1 / -1; }
    .workout-start-options #programStartSuggestion:empty { display: none; }
    .workout-start-options .program-next-wrap { margin: 0; }
      /* #417 redo (user 2026-09-13): "halo" means the wrap's lighter
         accent wash/outline, not a shadow — the v1.640 accent-tinted outer
         glow on the wrap is removed. What remains is exactly the Home
         treatment: the wrap's 10px padding + soft accent wash (the wash
         follows --accent per the Rosé override above), and the card keeps
         its standard elevation. The wrap keeps margin:0 — Home's negative
         margins break the start-options grid (#377). */
    .program-next-flexibility { margin: 8px 8px 0; }
    .program-next-flexibility summary { color: var(--muted); cursor: pointer; font-size: 11px; font-weight: 750; }
    .program-next-alternatives { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
    .program-next-alternatives button { min-height: 44px; padding: 0 10px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); cursor: pointer; font-size: 12px; font-weight: 750; }
    /* ===== #129: Workout start screen — Continue card + Saved workouts ===== */
    #continueWorkoutWrap { padding: 0 2px; }
    #continueWorkoutWrap[hidden] { display: none; }
    .continue-workout-card { width: 100%; display: grid; grid-template-columns: minmax(0,1fr) auto; align-items: center; gap: 16px; padding: 18px; border: 1px solid var(--accent); border-radius: var(--radius-md); background: var(--accent-soft); color: var(--ink); text-align: left; cursor: pointer; box-shadow: var(--shadow-soft); }
    .continue-kicker { display: block; margin-bottom: 5px; color: var(--accent); font-size: 10px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; }
    .continue-workout-card strong { display: block; font-size: 18px; line-height: 1.25; }
    .continue-workout-card small { display: block; margin-top: 5px; color: var(--muted); font-size: 11px; line-height: 1.35; }
    .continue-program { color: var(--ink); font-weight: 750; }
    .continue-arrow { color: var(--accent); font-size: 22px; font-weight: 800; }
    /* QA batch (user 2026-09-21): small bold "or start a new workout" link
       under the Continue card when a workout is already active. */
    .start-new-workout-link { display: block; width: 100%; margin: 10px 0 0; padding: 8px; border: 0; background: none; color: var(--accent); font-size: 13px; font-weight: 700; text-align: center; cursor: pointer; }
    /* User 2026-09-14: the Home instance of the continue card keeps the 16px
       gap above the dashboard grid that the old Home card had. */
    #workoutInProgressCard { margin-bottom: 16px; }
    #workoutInProgressCard[hidden] { display: none; }
    /* User 2026-09-14: the Continue-program quick jump sits at the top of
       Home, under the Workout-in-progress card. */
    #dashboardContinueProgram { margin-bottom: 16px; }
    #dashboardContinueProgram[hidden] { display: none; }
    #dashboardContinueProgram:empty { display: none; }
    /* Saved-workout draft card (user 2026-09-11): a draft, not a live session —
       bold dashed outline, no fill, quieter than the live Continue card. */
    .continue-workout-card.is-draft { background: transparent; border: 2px dashed var(--accent); box-shadow: none; }
    .training-saved { padding: 0 2px; }
    .saved-workout-cards { display: grid; gap: 10px; }
    .saved-workout-row .picker-item { margin-bottom: 0; }
    .archived-saved-disclosure { margin-top: 10px; }
    .archived-saved-disclosure > summary { display: flex; align-items: center; min-height: 44px; color: var(--muted); cursor: pointer; font-size: 12px; font-weight: 750; }
    .archived-saved-disclosure .picker-list { margin-top: 6px; }
    .workout-toolbar-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; }
    .last-session-line { margin: 6px 0 8px; color: var(--ink); font-size: 13px; }
    .last-session-line strong { color: var(--ink); }
    /* ===== 14. Toasts ===== */
    /* Q11: toast clears the bottom nav via the shared token — if the nav
       height changes, the toast follows instead of floating or overlapping.
       v1.007 (user 2026-09-12): raised +32px — the old +16px sat too low on
       phones, half-reading as part of the tab bar. */
    /* A6 (pixel-peeper PP1): the toast clears sticky bottom action areas
       (e.g. Save/Discard bars) as well as the bottom nav — it no longer
       sits on top of them. */
    .app-toast { position: fixed; z-index: 80; left: 50%; bottom: calc(var(--bottom-nav-clearance) + 88px); width: min(420px, calc(100% - 28px)); padding: 13px 16px; transform: translate(-50%, 6px); border: 1px solid var(--line-strong); border-radius: var(--radius-md); background: var(--ink-strong); color: var(--bg); box-shadow: var(--shadow); font-size: 13px; font-weight: 750; opacity: 0; transition: opacity var(--motion-exit) var(--ease-accelerate), transform var(--motion-exit) var(--ease-accelerate); pointer-events: none; }
    .app-toast.show { transform: translate(-50%, 0); opacity: 1; transition: opacity var(--motion-enter) var(--ease-decelerate), transform var(--motion-enter) var(--ease-decelerate); }
    /* #241 (user 2026-09-12): the PR toast read as transparent — gold-soft is an
       rgba wash in the dark themes. Mix the gold over the surface so the toast
       is fully opaque while keeping the gold tint. The plain --surface line
       is the pre-color-mix fallback (still opaque); the color-mix line wins
       where supported. */
    .app-toast.pr-toast { border-color: var(--gold); background: var(--surface); background: color-mix(in srgb, var(--gold) 16%, var(--surface)); color: var(--ink-strong); }
    /* Q2: error toasts mirror the gold PR treatment — danger border, ink
       text — so the 'error' kind is visible.
       #638 (user 2026-10-18): the danger-soft fill is a 10–14% alpha wash
       in the non-default themes, so the error toast read as see-through
       pink. It now follows the #241 PR-toast pattern exactly: opaque
       --surface fallback + the danger color mixed over the surface. */
    .app-toast.error { border-color: var(--danger); background: var(--surface); background: color-mix(in srgb, var(--danger) 16%, var(--surface)); color: var(--ink-strong); }
    .compact-dialog { width: min(460px, calc(100% - 28px)); }
    /* Share-link dialog (user 2026-09-12): the link itself is visible and
       selectable — no more copy-and-toast into the void. */
    .share-link-field { width: 100%; margin: 4px 0 2px; padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--bg); color: var(--muted); font-size: 12px; font-family: ui-monospace, monospace; white-space: nowrap; overflow-x: auto; }
    /* #236 calm mint: while a share link is being prepared the field shows
       the same quiet skeleton shimmer as the loading skeletons — no
       building-state text, no spinner. */
    .share-link-field.is-building { background: linear-gradient(100deg, var(--surface-2) 40%, var(--line) 50%, var(--surface-2) 60%); background-size: 200% 100%; animation: skel-shimmer 1.4s linear infinite; color: transparent; }
    @media (prefers-reduced-motion: reduce) { .share-link-field.is-building { animation: none; } }
    /* 2026-09-23: short-link mint-failure reason under the share field. */
    .share-link-note { margin: 6px 2px 0; color: var(--muted); font-size: 12px; text-align: center; }
    /* Stacked dialog actions (user 2026-09-10): three buttons don't fit
       side-by-side at 393px, and stacking separates the destructive choice. */
    .compact-dialog .form-actions { flex-direction: column; align-items: stretch; }
    .compact-dialog .form-actions .primary-button,
    .compact-dialog .form-actions .secondary-button { width: 100%; }
    /* #225: share-info step — Copy link first (primary), Share… second with
       the share glyph, Done as a quiet single-line text link beneath. */
    .share-info-actions .share-with-icon { display: inline-flex; align-items: center; justify-content: center; gap: 8px; }
    .share-info-actions .share-with-icon svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
    .share-info-actions .text-link { align-self: center; margin-top: 4px; }
    /* #199 (user 2026-09-12) SUPERSEDED by #639 (user 2026-10-18) for this
       dialog only: "Keep editing" is no longer a quiet text link — it is a
       secondary button stacked with Finish workout inside .form-actions
       (the compact-dialog column rules give both width 100%, and #633
       gives both the 44px tier). The share dialog's Done link keeps the
       #199/#225 quiet-link treatment. */
    #reviewSetsDialog .review-keep-editing { display: block; margin: 2px auto 0; }
    /* #501 (v1.8): the finish-review list of unmarked-but-valid sets —
       per-set toggles so the choice isn't all-or-nothing. */
    .review-sets-list { list-style: none; margin: 6px 0 2px; padding: 0; display: flex; flex-direction: column; gap: 6px; max-height: 38dvh; overflow: auto; }
    .review-set-row { display: flex; align-items: center; gap: var(--space-3); width: 100%; padding: 10px var(--space-3); border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--bg); color: var(--ink); font: inherit; text-align: left; cursor: pointer; }
    .review-set-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
    .review-set-text strong { font-size: 13px; font-weight: 600; }
    .review-set-text span { color: var(--muted); font-size: 12px; }
    .review-set-check { flex: none; width: 24px; height: 24px; }
    .review-set-check svg { width: 24px; height: 24px; display: block; }
    .review-set-check .box { fill: none; stroke: var(--muted); stroke-width: 1.8; }
    .review-set-check .tick { fill: none; stroke: transparent; stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round; }
    /* #501: the completed-log marker for sets the finish explicitly left
       unchecked — quiet chip on its own grid row, same vocabulary as the
       dialog's "Leave unchecked". */
    .done-set-unmarked { grid-column: 1 / -1; justify-self: end; font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); border: 1px solid var(--line); border-radius: 999px; padding: 2px 8px; white-space: nowrap; }
    /* ===== 15. Heat-level fills (shared by the Sasha body map regions and swatches) ===== */
    .heat-0 { fill: var(--heat-0); background: var(--heat-0); } .heat-1 { fill: var(--heat-1); background: var(--heat-1); } .heat-2 { fill: var(--heat-2); background: var(--heat-2); } .heat-3 { fill: var(--heat-3); background: var(--heat-3); } .heat-4 { fill: var(--heat-4); background: var(--heat-4); } .heat-5 { fill: var(--heat-5); background: var(--heat-5); }
    /* Phone QA 2026-09-12: completed-workout maps highlight every worked region in one flat tone — no heat ranking. */
    .heat-worked { fill: var(--heat-4); }
    /* ===== 16. Settings ===== */
    .settings-grid { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 10px; margin-bottom: 8px; }
    .settings-actions { display: flex; flex-wrap: wrap; gap: 10px; }
    /* A3 (pixel-peeper PP1): the Data backup actions stack as full-width
       rows instead of wrapping 2+1. */
    #settingsView .settings-section[data-section="data"] .settings-actions { flex-direction: column; align-items: stretch; }
    #settingsView .settings-section[data-section="data"] .settings-actions .small-button { width: 100%; }
    #accountSignedOut .settings-actions { margin-top: 14px; }
    /* #224: the inline sign-in form's action pills ("Email me a code",
       "Sign in") sat jammed against the fields above them — give them
       breathing room. Settings keeps its own rule above. */
    #shareSignInDialog .settings-actions { margin-top: 12px; }
    .small-button.danger { border-color: var(--rose); color: var(--rose); }
    .attribution-list { margin: 0; padding-left: 18px; display: grid; gap: 8px; color: var(--muted); font-size: 13px; line-height: 1.5; }
    .attribution-list a { color: var(--accent); }
    @container (max-width: 520px) { .settings-grid { grid-template-columns: 1fr; } }
    /* Settings polish — airy stacked headers, generous rhythm */
    #settingsView .dashboard-card { margin-bottom: 18px; padding: 22px; }
    #settingsView .dashboard-card:last-of-type { margin-bottom: 8px; }
    .settings-head { display: block; margin-bottom: 20px; }
    .settings-head h2 { margin: 0 0 6px; font-size: 19px; letter-spacing: -.02em; }
    .settings-head .section-note { font-size: 13px; line-height: 1.55; max-width: 46ch; }
    /* #502/#408 (user 2026-09-16): Settings sections are a single-open
       accordion — all start collapsed, opening one closes the others.
       Reuses the disclosure-chevron vocabulary from .similar-disclosure. */
    #settingsView details.settings-section, #settingsView section.settings-section { padding: 0; }
    #settingsView .settings-summary { list-style: none; display: flex; align-items: center; gap: 12px; margin: 0; padding: 16px 22px; cursor: pointer; }
    #settingsView .settings-summary::-webkit-details-marker { display: none; }
    #settingsView .settings-summary-copy { flex: 1; min-width: 0; }
    #settingsView .settings-summary h2 { margin: 0; }
    #settingsView .settings-summary .section-note { display: block; margin: 5px 0 0; }
    #settingsView .settings-section[open] > .settings-summary { padding-bottom: 10px; }
    #settingsView .settings-section-body { padding: 0 22px 22px; }
    /* Compact account card keeps its tight padding in accordion form. */
    #settingsView .account-card > .settings-summary { margin: 0; padding: 14px 16px; }
    #settingsView .account-card > .settings-section-body { padding: 0 16px 16px; }
    /* user 2026-09-16: Account is always expanded — the header is a plain
       div now (no <summary>/chevron), styled like the old summary. */
    #settingsView .account-card > .section-head { margin: 0; padding: 14px 16px; }
    /* The progression card's corner info button overlaps the summary's
       right edge — hold the chevron clear of it. */
    #progressionDefaultsCard > .settings-summary { padding-right: 56px; }
    /* Compact account card (user 2026-09-11): email directly under the
       heading, no description, no chevron, tight padding. */
    #settingsView .account-card { padding: 14px 16px; }
    #settingsView .account-card .settings-head { margin-bottom: 2px; }
    #settingsView .account-card .settings-head h2 { margin: 0; }
    #settingsView .account-card .account-row { min-height: 0; padding: 0; justify-content: flex-start; }
    /* #37: calmer account dialog — text-style actions instead of colorful buttons. */
    #accountDialog .form-actions { flex-direction: column; align-items: stretch; gap: 2px; }
    #accountDialog .form-actions button { border: 0; background: none; padding: 10px 4px; text-align: left; font-size: 14px; font-weight: 600; color: var(--ink); cursor: pointer; }
    #accountDialog .form-actions button.danger-text { color: var(--danger); }
    #settingsView .account-card #accountRowEmail { font-size: 14px; color: var(--muted); }
    #settingsView .account-card #accountStatus:empty { display: none; }
    /* OTP code sign-in (2026-09-11): divider between the magic-link button
       and the code field. The code is the reliable path on the iPhone
       home-screen app, whose storage is isolated from Safari. */
    /* #75: OTP step is revealed after the email is sent (progressive disclosure). */
    #accountSignedOut #otpStep { margin-top: 4px; }
    /* Title row: heading with an inline info button (progression explainer). */
    .settings-title-row { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
    .settings-title-row h2 { margin: 0; }
    .settings-title-row .exercise-info-button { width: 28px; height: 28px; font-size: 13px; }
    /* Grouped subsections inside the progression card (rep range, time step). */
    .settings-subsection { grid-column: 1 / -1; display: grid; gap: 10px; }
    .settings-subhead { color: var(--muted); font-size: 10px; font-weight: 800; letter-spacing: .11em; text-transform: uppercase; font-variant-caps: all-small-caps; }
    /* Progression explainer popup body. */
    .info-list { display: grid; gap: 12px; margin: 6px 0 10px; }
    .info-list p { margin: 0; display: grid; gap: 3px; font-size: 13px; line-height: 1.55; }
    .info-list strong { color: var(--ink-strong); }
    .info-list span { color: var(--muted); }
    /* Library headings with an inline text action ("+ New exercise"). */
    .library-heading-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin: 0 2px 10px; }
    .library-heading-row .library-heading { margin: 0; }
    .text-link { border: 0; background: none; padding: 4px 2px; color: var(--accent); font-size: 13px; font-weight: 700; cursor: pointer; white-space: nowrap; }
    .text-danger-button { border: 0; background: none; padding: 4px 2px; color: var(--danger); font-size: 13px; font-weight: 700; cursor: pointer; white-space: nowrap; align-self: flex-start; }
    /* #142: swap-exercise action — borderless like Remove, but a real 44px
       tap target (the #94 lesson). */
    .swap-workout-exercise { border: 0; background: none; padding: 4px 2px; min-height: 44px; display: inline-flex; align-items: center; color: var(--accent); font-size: 13px; font-weight: 700; cursor: pointer; white-space: nowrap; align-self: flex-start; }
    .text-link:active { transform: scale(.96); }
    /* Q13: 44px tap targets via the invisible ::before hit-area shim (same
       pattern as .rule-remove) — visual size unchanged. */
    .see-all-link, .filter-clear, .text-link, .text-danger-button { position: relative; }
    .see-all-link::before, .filter-clear::before, .text-link::before, .text-danger-button::before { content: ""; position: absolute; left: 50%; top: 50%; width: 44px; height: 44px; transform: translate(-50%, -50%); }
    /* Non-italic glyph variant of the circular info button (used for "+"). */
    .exercise-info-button.plain-glyph { display: inline-flex; align-items: center; justify-content: center; font-style: normal; font-size: 20px; font-weight: 500; line-height: 1; }
    /* × remove button inside a picker rule accordion head. */
    .rule-head-actions { display: inline-flex; align-items: center; gap: 10px; flex: none; }
    .rule-remove { position: relative; width: 30px; height: 30px; border-radius: 50%; border: 1px solid var(--danger); background: var(--surface); color: var(--danger); font-size: 17px; line-height: 1; cursor: pointer; padding: 0 0 2px; } /* #209 (user 2026-09-12): the delete × in the saved-workout editor renders in danger red, matching the app's destructive-control convention. */
    .rule-remove::before { content: ""; position: absolute; left: 50%; top: 50%; width: 44px; height: 44px; transform: translate(-50%, -50%); }
    .rule-remove:active { transform: scale(.92); }
    .settings-grid { gap: 16px; margin-bottom: 6px; grid-auto-flow: dense; }
    /* Paired fields (increment type+value, rep min+max) always sit side by side. */
    .settings-pair { grid-column: 1 / -1; display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 16px; }
    #settingsView .rule-field { gap: 8px; }
    #settingsView .rule-field > span:first-child { font-size: 10px; letter-spacing: .11em; }
    /* #316 (user 2026-09-13): Settings inputs follow the workout-set input
       styling exactly — the old 48px/14px/12px ad-hoc override is gone. */
    #settingsView .rule-field input, #settingsView .rule-field select { min-height: 44px; height: 44px; padding: 0 9px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface-raised); color: var(--ink); font-size: 16px; }
    #settingsView .switch-row { margin-top: 12px; padding: 18px 0 2px; border-top: 1px solid var(--line); }
    #settingsView .switch-copy strong { font-size: 14px; }
    #settingsView .switch-copy span { font-size: 12px; line-height: 1.5; }
    .settings-actions { gap: 12px; }
    /* #610/#615 (user 2026-10-18): ONE segmented-button spec across
       Settings — the Stats card mini-segmented spec (buttons
       min-height 36px, padding 0 var(--space-3), 12px / weight 750,
       radius-md; row gap var(--space-2)) — applied to every Settings
       pill group: step-pills (units / effort / stats default / time
       step), cycle-pills, theme-pills, rep-preset rows (scheme, focus,
       warm-up count), and the RPE trigger squares (their 44px square
       geometry yields to the shared spec). The .form-pill base rule
       carries the same spec for the option dialogs. Switches keep the
       approved mockup geometry (48x28 track) — untouched. */
    #settingsView .step-pills button, #settingsView .cycle-pills .cycle-pill,
    #settingsView .theme-pills [data-theme-name], #settingsView .rep-preset {
      min-height: 36px; padding: 0 var(--space-3); border-radius: var(--radius-md);
      font-size: 12px; font-weight: 750;
    }
    #settingsView #settingsRpePills .rep-preset { width: auto; height: auto; min-width: 0; }
    #settingsView .rep-preset-row { gap: var(--space-2); }
    /* #615: ONE card-title spec for every Settings card, collapsed or
       expanded — the summary h2 and the Account card's plain head
       share it (19px / 800 / -.02em, no margin drift). */
    #settingsView .settings-head h2, #settingsView .account-card > .section-head h2 {
      margin: 0; font-size: 19px; font-weight: 800; letter-spacing: -.02em;
    }
    #settingsView .attribution-list { gap: 10px; }
    .action-list { display: grid; gap: 8px; }
    .action-row { width: 100%; display: grid; grid-template-columns: minmax(0,1fr) auto; gap: 12px; align-items: center; min-height: 52px; padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface-raised); color: var(--ink); text-align: left; cursor: pointer; }
    .action-row span { color: var(--muted); font-size: 11px; }
    .action-row strong { display: block; }
    .action-row-value { color: var(--accent); font-size: 13px; font-weight: 850; }
    .shell { padding-top: var(--top-bar-clearance); }
    .calendar-toolbar { grid-template-columns: 46px minmax(0,1fr) 46px; gap: 16px; margin-bottom: 14px; }
    .calendar-toolbar strong { font-size: 14px; letter-spacing: -.01em; }
    .week-strip { gap: 7px; padding-bottom: 16px; }
    .day-chip { min-height: 64px; padding: 10px 2px 8px; overflow: hidden; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; border-radius: 16px; }
    .day-chip span, .day-chip strong { margin-top: 0; }
    .day-chip.has-workout strong::after { display: none; }
    .day-chip em { display: block; min-height: 6px; margin: 0; }
    .day-chip em i { display: block; width: 5px; height: 5px; border-radius: 50%; background: var(--accent); }
    .dashboard-card-head { align-items: flex-start; margin-bottom: 22px; }
    .mini-stats { margin-top: 2px; }
    .advanced-options { margin-top: 12px; border: 0; border-top: 1px solid var(--line); border-radius: 0; background: transparent; }
    .advanced-options summary { min-height: 44px; padding: 0 3px; color: var(--muted); }
    /* Quiet tertiary action under the pill buttons (user 2026-09-10): keeps
       "repeat this workout" reachable without the big button he removed. */
    /* Completed-workout bottom buttons (#73, user 2026-09-11): Repeat is the
       primary full-width action; Edit + Delete sit side-by-side below it. */
    .detail-action-buttons { margin-top: 20px; display: flex; flex-direction: column; gap: 10px; }
    .detail-action-primary { width: 100%; min-height: var(--btn-h-lg); font-size: 15px; }
    .detail-action-row { display: flex; gap: 10px; }
    .detail-action-row .secondary-button { flex: 1; min-height: 44px; }
    /* Share preview (user 2026-09-12): Start + Add sit side-by-side. */
    .detail-action-row .primary-button { flex: 1; min-height: 44px; }
    /* #129 rework (user phone QA 2026-09-12): with a live session the start
       screen keeps the saved list visible under the Continue card. */
    .training-home.draft-live .training-hero { display: none; }
    /* Saved-workout builder (user 2026-09-11): full-page creator mirroring the
       live editor — name, focus pills, per-set targets — with "Save workout"
       instead of "Finish workout" and no complete checkboxes. */
    /* Saved-workout signifier (user 2026-09-11): marks the builder page as a
       saved-workout draft, echoing the dashed draft language. */
    .builder-signifier { margin: 2px 0 12px; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
    .saved-pill { display: inline-flex; align-items: center; gap: 6px; padding: 6px 13px; border: 1.5px dashed var(--accent); border-radius: 999px; background: transparent; color: var(--accent); font-size: 11px; font-weight: 800; letter-spacing: .07em; text-transform: uppercase; }
    /* v0.99w (user 2026-09-12): "Part of <program>" chip — same dashed
       language as the saved pill, muted so the Saved workout pill leads. */
    .saved-pill.program-part-pill { border-color: var(--line); color: var(--muted); text-transform: none; letter-spacing: 0; font-size: 12px; }
    /* v0.99w (user phone QA 2026-09-12): Start sits inline with the workout
       name, top-right — a compact accent button saying just "Start". */
    .detail-title-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
    .detail-title-row h2 { margin: 0; min-width: 0; }
    /* #252: the completed view's Edit lives in the title row now — keep the
       two inline actions grouped and shrink-proof. */
    .detail-title-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
    .start-inline-button { flex: none; min-height: 44px; padding: 0 24px; border: 0; border-radius: var(--radius-md); background: var(--accent); color: var(--surface-raised); font-size: 15px; font-weight: 800; cursor: pointer; }
    .start-inline-button:active { background: var(--accent-hover); }
    /* Title-row action cluster (user 2026-09-12): Share sits beside Start. */
    .title-actions { flex: none; display: inline-flex; align-items: center; gap: 8px; }
    /* #633 (user 2026-10-18): the title-row action pairs (share icon +
       Start/Repeat at 44px) share one height — the icon box rises from
       the 40px --icon-btn tier to var(--btn-h) beside its sibling. */
    .detail-title-actions .icon-button, .title-actions .icon-button { width: var(--btn-h); height: var(--btn-h); }
    .icon-button { flex: none; display: inline-flex; align-items: center; justify-content: center; width: var(--icon-btn); height: var(--icon-btn); padding: 0; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); cursor: pointer; }
    .icon-button:active { border-color: var(--accent); color: var(--accent); }
    .icon-button svg { width: 20px; height: 20px; }
    /* v0.99w (user phone QA 2026-09-12): program workout cards are <button>s —
       pin their width so they fill the row exactly like the saved-workout
       cards instead of shrink-wrapping inside the swipe container. */
    .program-workouts .swipe-content { width: 100%; }
    /* User 2026-09-12: program workout cards were "missing their corners" —
       the .swipe-item wrapper (radius-md, overflow hidden) showed its square
       background corners around the smaller-radius inner card on iOS. The
       wrapper goes transparent and the card itself takes the full radius. */
    .program-workouts .swipe-item { background: transparent; }
    .program-workouts .swipe-content { border-radius: var(--radius-lg); }
    /* #310 (user 2026-09-12): saved-workout cards had the same "missing
       corners" iOS quirk as the program cards — the .swipe-item wrapper
       showed its square background corners around the smaller-radius inner
       card. Same treatment: transparent wrapper, full radius on the card.
       #641 (user 2026-10-18): the corners went missing AGAIN — the #599
       radius unification moved the .swipe-item clip to var(--radius-lg)
       while the card stayed at var(--radius-md); the wrapper's
       overflow:hidden clips along its own 16px curve and shears the
       card's 12px corner (and its border) off. The card radius must EQUAL
       the wrapper clip radius, so both lists take var(--radius-lg). */
    .saved-workout-cards .swipe-item { background: transparent; }
    .saved-workout-cards .swipe-content { border-radius: var(--radius-lg); }
    /* Visible delete button on each program workout card (user 2026-09-12):
       swipe-to-delete stays, but the × is discoverable. The row is a div
       holding two real buttons (open + delete) — no nested interactives. */
    .program-workout-row { display: flex; align-items: stretch; gap: 0; padding: 0; }
    /* User 2026-09-12: the chevron gets the same breathing room as the
       saved-workout cards (12px), not jammed against the card edge. */
    .program-workout-open { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 12px; border: 0; background: none; color: inherit; font: inherit; text-align: left; cursor: pointer; }
    .program-workout-open > span:first-child { min-width: 0; }
    .program-workout-open strong { display: block; margin-bottom: 4px; color: var(--ink); font-size: 14px; }
    .program-workout-open > span:first-child > span { color: var(--muted); font-size: 11px; text-transform: capitalize; }
    /* #260: saved-workout rows get the same swipe-to-delete treatment as
       program rows — the open button is the row body, so swipes may start
       on it (tap-vs-swipe is still decided at release). */
    .saved-workout-row { display: flex; align-items: stretch; gap: 0; padding: 0; }
    .saved-workout-open { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 12px; border: 0; background: none; color: inherit; font: inherit; text-align: left; cursor: pointer; }
    .saved-workout-open > span:first-child { min-width: 0; }
    .saved-workout-open strong { display: block; margin-bottom: 4px; color: var(--ink); font-size: 14px; }
    .saved-workout-open > span:first-child > span { color: var(--muted); font-size: 11px; text-transform: capitalize; }
    .program-workout-del, .saved-workout-del { flex: 0 0 46px; display: flex; align-items: center; justify-content: center; border: 0; border-left: 1px solid color-mix(in srgb, var(--line) 40%, transparent); border-radius: 0 var(--radius-lg) var(--radius-lg) 0; background: none; color: var(--muted); cursor: pointer; }
    /* #324 (user 2026-09-12): hovering the × tints that part of the card the
       swipe-to-delete rail red — the same affordance as the swipe rail. */
    .program-workout-del:hover, .program-workout-del:focus-visible,
    .saved-workout-del:hover, .saved-workout-del:focus-visible { background: var(--swipe-rail-bg); color: var(--on-accent); outline: none; }
    .builder-targets-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 0; }
    .builder-targets-row .range-summary { color: var(--muted); font-size: 13px; }
    /* #208 (user 2026-09-13): per-exercise rep-range pills in the builder —
       same pill buttons as the workout-focus row, compact under the targets. */
    .builder-range-row { padding: 0 0 8px; }
    .builder-range-row .range-summary { color: var(--muted); font-size: 13px; display: block; margin-bottom: 6px; }
    .builder-range-row .rep-preset-row { margin: 0; }
    /* Saved-builder set rows mirror the live editor's label grid (user
       2026-09-11): SET / REPS-or-SECONDS over number / target input / delete. */
    .log-labels.builder-labels{grid-template-columns:44px minmax(0,1fr) 44px;}
    .log-labels.builder-labels span:first-child{text-align:left;padding-left:14px;}
    .builder-set-row{display:grid;grid-template-columns:44px minmax(0,1fr) 44px;gap:6px;align-items:center;padding:10px 0;}
    /* P1 QA 2026-09-22: visible set-tag chips under template-builder rows —
       warm-up rows were indistinguishable from work sets. */
    .builder-set-tags{display:flex;flex-wrap:wrap;gap:4px;padding:0 0 8px 50px;}
    .builder-set-row + .builder-set-row{border-top:1px solid var(--line);}
    /* #339: builder set rows swipe like the live editor's — the swipe rail
       replaces the × when swipe-to-delete is on (touch); the × stays as the
       delete path when it's off (desktop). The grid drops the × column with
       it so the input keeps full width. */
    .is-touch.swipe-sets .builder-set-row .builder-x{display:none;}
    .is-touch.swipe-sets .builder-set-row{grid-template-columns:44px minmax(0,1fr);}
    .is-touch.swipe-sets .log-labels.builder-labels{grid-template-columns:44px minmax(0,1fr);}
    .is-touch.swipe-sets .log-labels.builder-labels span:last-child{display:none;}
    .builder-set-num{display:grid;place-items:center;color:var(--muted);font-size:13px;font-weight:800;min-height:44px;font-variant-numeric:tabular-nums;}
    .builder-set-row .log-input{width:100%;}
    .builder-x{border:1px solid var(--danger);background:transparent;color:var(--danger);border-radius:50%;width:28px;height:28px;line-height:1;flex-shrink:0;justify-self:center;}
    /* Saved-workout editor exercise rows are read-only summaries. */
    .saved-editor-row { cursor: default; margin-bottom: 8px; }
    @container (max-width: 520px) {
      .shell { padding: var(--top-bar-clearance) 14px calc(112px + env(safe-area-inset-bottom)); } /* #120: was 205px */
      .workout-start-options { grid-template-columns: 1fr; }
      .workout-toolbar { align-items: center; }
      /* Icon-only toolbar buttons (reorder, add) stay compact and
         right-aligned — never stretched into full-width pills
         (user 2026-09-11). */
      .workout-toolbar-actions { width: auto; }
      .workout-toolbar-actions button { flex: 0 0 auto; }
      .log-labels { display: grid; }
      .calendar-toolbar { gap: 12px; }
      .calendar-toolbar span { display: none; }
    }
  
    /* ===== Desktop mode (laptop testing, user 2026-09-10) =====
       Purely additive: this block only applies at viewport widths >= 900px, so
       the phone layout is untouched. Centers the app in a column exactly
       393px wide — user's iPhone 14 Pro CSS resolution — so the desktop
       render is pixel-identical to the phone, just centered in the browser. */
    @media (min-width: 900px) {
      .app { width: 100%; max-width: 393px; margin: 0 auto; border-left: 1px solid var(--line); border-right: 1px solid var(--line); }
      .top-bar, .bottom-nav { left: 50%; right: auto; width: min(393px, 100%); transform: translateX(-50%); }
    }

    /* ===== #98: optional desktop layout — marketing-site chrome (user 2026-09-16) =====
       Opt-in via Settings → Desktop layout (off by default). Everything is
       scoped to body.desktop: the phone layout is untouched and there is no
       automatic breakpoint — the user chooses.
       2026-09-16 follow-up (user): the desktop chrome looks like the
       marketing site — a sticky blurred header carrying the brand mark, the
       five tabs as header links, and the settings gear. The bottom tab bar is
       gone in desktop mode; the slim top bar drops below the header as the
       contextual strip (back chevron + page title + live chip), and cards get
       the marketing treatment (white, hairline border, soft shadow) on the
       pale-green paper background. The marketing palette applies whenever
       desktop mode is on, regardless of the active app theme (the marketing
       site is light-only). */
    body.desktop {
      --bg: #f8f9f3; --surface: #ffffff; --surface-2: #edf0e2; --surface-raised: #ffffff;
      --ink: #1c1c1e; --muted: #5a6355; --line: #dde2d2; --line-strong: #b7bfa6;
      --accent-soft: #e6f4d2; --pine-soft: #e6f4d2;
      --nav-bg: rgba(248,249,243,.94); --theme-color: #f8f9f3;
      /* user 2026-09-16: pin accent/danger to the marketing greens. Dark-theme
         accents (peach/mint) wash out on the light shell — the active header
         tab and switch on-states blended into the background. */
      --accent: #2e7d32; --accent-hover: #256429; --danger: #d92d20;
    }
    body.desktop .app { max-width: 1120px; }
    /* The desktop header is display:none unless body.desktop. It is fixed
       like the top bar, so the top bar shifts down below it. */
    .desktop-header { display: none; }
    body.desktop .desktop-header {
      display: block; position: fixed; top: 0; left: 50%; right: auto;
      width: min(1120px, 100%); transform: translateX(-50%); z-index: 31;
      border-bottom: 1px solid var(--line);
      background: rgba(248,249,243,.94);
      -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
    }
    body.desktop .desktop-header-inner {
      height: 72px; margin: 0 auto; padding: 0 24px;
      display: flex; align-items: center; gap: 28px;
    }
    body.desktop .desktop-brand {
      display: inline-flex; align-items: center; gap: 10px; padding: 0;
      background: none; border: 0; cursor: pointer; font: inherit;
      font-size: 18px; font-weight: 820; letter-spacing: -.02em;
      color: var(--ink); text-decoration: none; white-space: nowrap;
    }
    body.desktop .desktop-brand svg { width: 32px; height: 32px; border-radius: 9px; display: block; }
    body.desktop .desktop-tabs { display: flex; align-items: center; gap: 4px; margin-left: auto; }
    body.desktop .desktop-tab {
      border: 0; background: transparent; color: #3d443a; font: inherit;
      font-size: 14px; font-weight: 670; padding: 10px 14px; border-radius: 12px;
      cursor: pointer; white-space: nowrap;
    }
    body.desktop .desktop-tab:hover { color: var(--accent); }
    body.desktop .desktop-tab.active { background: var(--accent-soft); color: var(--accent); }
    body.desktop .desktop-gear {
      display: grid; place-items: center; width: 44px; height: 44px; flex: none;
      border: 0; border-radius: 12px; background: transparent; color: var(--ink); cursor: pointer;
    }
    body.desktop .desktop-gear svg { width: 22px; height: 22px; }
    body.desktop .desktop-gear:hover { background: var(--accent-soft); }
    body.desktop .desktop-gear.active { background: var(--accent-soft); color: var(--accent); }
    /* Contextual strip: the top bar keeps back chevron + title + live chip.
       Its gear is redundant — the desktop header carries settings now. */
    body.desktop .top-bar { top: 72px; left: 50%; right: auto; width: min(1120px, 100%); transform: translateX(-50%); }
    body.desktop .bottom-nav { display: none; }
    body.desktop #topBarSettings { display: none; }
    body.desktop .shell { padding-top: calc(var(--top-bar-clearance) + 72px + 16px); }
    html:has(> body.desktop) { scroll-padding-top: calc(var(--top-bar-clearance) + 72px); }
    /* Marketing-site rhythm on the wide shell: airier sections, a touch
       more type scale on headings, cards with the marketing shadow. */
    body.desktop .dashboard-card { padding: 24px; box-shadow: 0 6px 18px rgba(28,28,30,.05); }
    body.desktop .dashboard-card h2 { font-size: 19px; }
    body.desktop .top-bar-title { font-size: 19px; }
    /* user 2026-09-16: the Desktop layout option only exists on wide screens
       — hidden on mobile viewports. The stored choice still only applies at
       desktop widths (applyDesktopLayout is viewport-gated). */
    @media (max-width: 899px) {
      .desktop-layout-row { display: none; }
    }

/* ===== First-run onboarding gate (2026-09-11) =====
   Full-screen overlay shown once to new users before the main app boots.
   Uses the app's design tokens; hidden entirely once onboarding completes.
   #506 follow-up (user 2026-09-16): the standalone import overlay is opened
   from the onboarding import screen, so it must paint ABOVE the onboarding
   flow takeover (#onboardingFlow is z-index 300). */
#onboardingGate{position:fixed;inset:0;z-index:320;background:var(--bg);color:var(--ink);overflow-y:auto;-webkit-overflow-scrolling:touch;}
#onboardingGate[hidden]{display:none;}
body.ob-active{overflow:hidden;}
body.ob-active .app,body.ob-active .top-bar,body.ob-active .bottom-nav{visibility:hidden;}
.ob-phone{width:100%;max-width:393px;min-height:100vh;margin:0 auto;display:flex;flex-direction:column;padding:0 20px calc(24px + env(safe-area-inset-bottom));}
.ob-header{padding-top:calc(12px + env(safe-area-inset-top));display:flex;align-items:center;gap:8px;min-height:56px;}
.ob-back{border:0;background:none;font-size:28px;line-height:1;color:var(--ink);width:44px;height:44px;border-radius:var(--radius-md);display:none;cursor:pointer;padding:0;}
.ob-back.show{display:block;}
.ob-back:active{background:var(--line);}
.ob-progress{flex:1;text-align:right;font-size:13px;color:var(--muted);}
.ob-close{border:0;background:none;font-size:28px;line-height:1;color:var(--ink);width:44px;height:44px;border-radius:var(--radius-md);cursor:pointer;padding:0;margin-left:auto;}
.ob-close:active{background:var(--line);}
.ob-main{flex:1;display:flex;flex-direction:column;}
.ob-screen{animation:obFade var(--motion-page) var(--ease-standard);padding-bottom:24px;}
@keyframes obFade{from{opacity:0}to{opacity:1}}
.ob-kicker{display:inline-block;font-size:12px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--accent);margin-bottom:10px;}
.ob-h1{font-size:30px;line-height:1.15;margin:0 0 10px;color:var(--ink);}
.ob-sub{color:var(--muted);font-size:16px;line-height:1.5;margin:0 0 18px;}
.ob-hint{color:var(--muted);font-size:13px;line-height:1.45;margin:6px 0 12px;}
.ob-note{color:var(--muted);font-size:14px;min-height:20px;margin:10px 0;}
.ob-muted{color:var(--muted);}
.ob-input{width:100%;font-size:16px;padding:12px 14px;border:1px solid var(--line);border-radius:var(--radius-md);background:var(--surface);color:var(--ink);}
.ob-input:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--focus-ring);}
.ob-btn-primary{display:block;width:100%;margin:18px 0 0;padding:15px;border:0;border-radius:var(--radius-md);background:var(--accent);color:var(--on-accent);font-size:17px;font-weight:700;cursor:pointer;}
.ob-btn-primary:active{background:var(--accent-hover);}
.ob-btn-secondary{display:block;width:100%;margin:12px 0 0;padding:14px;border:1px solid var(--line-strong);border-radius:var(--radius-md);background:var(--surface);color:var(--ink);font-size:16px;font-weight:600;cursor:pointer;}
.ob-btn-link{display:block;width:100%;margin:12px 0 0;padding:12px;border:0;background:none;color:var(--accent);font-size:15px;font-weight:600;cursor:pointer;text-align:center;}
.ob-template{margin:2px 0 0;text-align:center;font-size:15px;font-weight:600;}
.ob-template a{color:var(--accent);text-decoration:none;}
.ob-template a:active{text-decoration:underline;}
.ob-row{display:flex;gap:10px;align-items:center;}
.ob-row .ob-btn-secondary,.ob-row .ob-btn-link{margin-top:18px;}
.ob-upload{border:2px dashed var(--line-strong);border-radius:var(--radius-lg);padding:28px 16px;text-align:center;cursor:pointer;background:var(--surface);margin:6px 0 4px;color:var(--ink);}
.ob-upload:active{background:var(--surface-2);}
.ob-upload-icon{font-size:28px;color:var(--accent);margin-bottom:8px;}
.ob-help{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius-md);padding:12px 14px;font-size:13px;color:var(--muted);line-height:1.5;margin:8px 0;}
.ob-help code{font-family:ui-monospace,monospace;font-size:12px;color:var(--ink);}
.ob-imp-err{background:var(--danger-soft);border:1px solid var(--danger);color:var(--danger);border-radius:var(--radius-md);padding:12px 14px;font-size:14px;margin:12px 0;}
.ob-imp-ok{background:var(--accent-soft);border:1px solid var(--accent);color:var(--accent);border-radius:var(--radius-md);padding:12px 14px;font-size:14px;margin:12px 0;font-weight:600;}
.ob-imp-sum{font-size:14px;color:var(--ink);margin:12px 0;}
.ob-imp-workout{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius-md);padding:12px 14px;margin:10px 0;font-size:14px;}
.ob-imp-workout.skipped{opacity:.45;}
.ob-wskip{display:block;margin:8px 0 4px;font-size:13px;color:var(--muted);}
.ob-imp-ex{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin:8px 0;font-size:13px;}
.ob-imp-nm{color:var(--muted);}
.ob-imp-no{color:var(--warning);font-weight:600;}
.ob-imp-sel{width:100%;margin-top:2px;font-size:14px;}
/* #139 "Use existing exercise" inline library search. */
.ob-imp-existing{width:100%;margin-top:2px;}
.ob-imp-exsearch{width:100%;font-size:14px;}
.ob-imp-exresults{display:flex;flex-direction:column;gap:4px;margin-top:6px;max-height:220px;overflow:auto;}
.ob-imp-exhit{display:flex;gap:6px;align-items:baseline;width:100%;text-align:left;background:var(--surface-2);border:1px solid var(--line);border-radius:var(--radius-md);padding:8px 10px;font-size:13px;color:var(--ink);cursor:pointer;}
.ob-imp-exhit:active{background:var(--surface-raised);}
.ob-imp-exchosen{width:100%;margin-top:4px;font-size:13px;display:flex;gap:8px;align-items:center;flex-wrap:wrap;}
.ob-imp-note{font-size:12px;color:var(--muted);margin:-4px 0 8px;}
.ob-imp-toggle{display:flex;align-items:center;gap:6px;width:100%;background:none;border:0;padding:10px 0;font-size:13px;color:var(--accent);font-weight:600;cursor:pointer;text-align:left;}
/* #286: the "Don't show again" row under the sign-in nudge actions. */
.nudge-dont-show{display:flex;align-items:center;gap:8px;margin:12px 2px 0;font-size:13px;color:var(--muted);cursor:pointer;}
.nudge-dont-show input{width:16px;height:16px;accent-color:var(--accent);}
/* #511 (user 2026-09-16): Privacy subsection checkboxes (Settings → Data).
   Label rows with a native checkbox + two-line copy. */
.privacy-check{display:flex;align-items:flex-start;gap:10px;margin:10px 2px 0;cursor:pointer;}
.privacy-check input{width:16px;height:16px;flex:none;margin-top:2px;accent-color:var(--accent);cursor:pointer;}
.privacy-check strong{display:block;font-size:14px;font-weight:700;}
.privacy-check small{display:block;font-size:13px;color:var(--muted);margin-top:2px;}
/* #506 follow-up (user 2026-09-16): hairline divider above Settings → Data →
   Privacy, separating the subscription prefs from the account controls. */
.privacy-subsection{border-top:1px solid var(--line);padding-top:14px;}

/* ===== Onboarding flow v2 (#506, v1.8) =====
   Short branching first-run: welcome → sign-in (optional) → setup choice →
   options (custom branch only) → import → how-it-works → done. Full-screen
   overlay that owns the boot for fresh profiles; hidden once the flow
   completes. The "of" prefix is the flow's own namespace — the legacy "ob"
   prefix belongs to the csv-import overlay above. */
#onboardingFlow{position:fixed;inset:0;z-index:300;background:var(--bg);color:var(--ink);overflow-y:auto;-webkit-overflow-scrolling:touch;}
#onboardingFlow[hidden]{display:none;}
body.of-active{overflow:hidden;}
body.of-active .app,body.of-active .top-bar,body.of-active .bottom-nav{visibility:hidden;}
/* #542 (user 2026-09-17 hotfix): pre-paint onboarding gate — index.html adds
   .ob-pending to <html> when the onboarding-done flag is absent (not a share
   boot or magic-link return). The app chrome stays invisible until
   app-bootstrap resolves the async onboarding decision, so the first paint is
   the neutral page background and the home tab can never flash underneath
   the onboarding flow. */
html.ob-pending .app,html.ob-pending .top-bar,html.ob-pending .bottom-nav{visibility:hidden;}
.of-phone{width:100%;max-width:393px;min-height:100vh;margin:0 auto;display:flex;flex-direction:column;}
.of-topbar{flex:none;min-height:56px;display:grid;grid-template-columns:52px 1fr 52px;align-items:center;border-bottom:1px solid var(--line);padding-top:calc(8px + env(safe-area-inset-top));}
.of-back{justify-self:center;background:none;border:0;font-size:26px;line-height:1;color:var(--ink);cursor:pointer;width:44px;height:44px;}
.of-back:active{color:var(--accent);}
.of-mark{width:30px;height:30px;border-radius:var(--radius-sm);justify-self:center;align-self:center;}
.of-title{text-align:center;font-size:17px;font-weight:700;letter-spacing:-.01em;}
#ofBody{flex:1;display:flex;flex-direction:column;min-height:0;}
.of-body{flex:1;display:flex;flex-direction:column;padding:28px 30px calc(48px + env(safe-area-inset-bottom));animation:ofFade var(--motion-page) var(--ease-standard);}
/* User 2026-09-17: buttons sit a touch higher — a little less dead space
   between step content and the button block. */
@keyframes ofFade{from{opacity:0}to{opacity:1}}
.of-center{align-items:center;text-align:center;}
/* #506 follow-up (user 2026-09-16): the welcome screen is vertically
   centered (replaces the old fixed-gap layout). */
.of-vcenter{justify-content:center;}
.of-logo{width:96px;height:96px;border-radius:24px;margin:8px 0 22px;box-shadow:var(--shadow-soft);}
.of-brand{font-size:30px;font-weight:800;letter-spacing:-.02em;margin-bottom:10px;}
.of-brand-sm{font-size:14px;font-weight:800;letter-spacing:.02em;color:var(--muted);margin-bottom:6px;}
.of-h1{font-size:22px;font-weight:750;letter-spacing:-.015em;margin:0 0 10px;}
.of-sub{font-size:15px;line-height:1.55;color:var(--muted);margin:0 0 26px;}
.of-center .of-sub{max-width:300px;}
.of-field{width:100%;margin-bottom:8px;text-align:left;}
.of-field label{display:block;font-size:13px;font-weight:700;color:var(--muted);margin-bottom:8px;letter-spacing:.02em;}
.of-input{width:100%;min-height:52px;border:1px solid var(--line);border-radius:var(--radius-md);background:var(--surface);padding:0 16px;font-size:16px;color:var(--ink);font-family:inherit;}
.of-input:focus{outline:2px solid var(--accent);outline-offset:-1px;border-color:var(--accent);}
.of-status{width:100%;min-height:20px;font-size:13.5px;color:var(--muted);margin:8px 0 0;}
.of-status.of-error{color:var(--danger);}
.of-spacer{flex:1;min-height:16px;}
.of-spacer.of-tight{max-height:40px;}
.of-cta{display:block;width:100%;min-height:52px;border:1px solid var(--accent);background:var(--accent);color:var(--on-accent);border-radius:var(--radius-md);font-size:16px;font-weight:700;cursor:pointer;box-shadow:none;font-family:inherit;}
.of-cta:active{background:var(--accent-hover);}
.of-cta.of-secondary{background:transparent;color:var(--accent);box-shadow:none;margin-top:12px;}
.of-quiet{margin-top:16px;background:none;border:0;color:var(--muted);font-size:14px;font-weight:600;cursor:pointer;text-decoration:underline;text-underline-offset:3px;font-family:inherit;align-self:center;}
a.of-quiet{display:inline-block;}
.of-cards{width:100%;display:grid;gap:10px;text-align:left;}
.of-card{display:block;width:100%;padding:16px;border:1px solid var(--line);border-radius:var(--radius-lg);background:var(--surface);cursor:pointer;font-family:inherit;color:var(--ink);text-align:left;}
.of-card:active{border-color:var(--accent);}
/* #506 follow-up: selectable setup-choice cards — the selected card shows
   the accent ring; Next commits the choice. */
.of-card.of-card-sel{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent);}
.of-card strong{display:block;font-size:15px;font-weight:750;margin-bottom:3px;}
.of-card span{font-size:13px;color:var(--muted);line-height:1.45;}
.of-tag{display:inline-block;font-size:11px;font-weight:800;letter-spacing:.04em;color:var(--accent);background:var(--accent-soft);border-radius:999px;padding:3px 9px;margin-left:6px;vertical-align:2px;}
.of-qgroup{margin-bottom:22px;}
.of-qgroup>span{display:block;font-size:13px;font-weight:700;color:var(--muted);margin-bottom:10px;letter-spacing:.02em;}
/* #514: one-line plain-language definition under a question-group label. */
.of-qsub{display:block;font-size:12px;color:var(--muted);line-height:1.45;margin:-6px 0 10px;}
.of-pills{display:flex;flex-wrap:wrap;gap:8px;}
.of-pill{border:1px solid var(--line);background:var(--surface);border-radius:var(--radius-md);padding:10px 16px;min-height:44px;font-size:14px;font-weight:600;color:var(--ink);cursor:pointer;font-family:inherit;}
.of-pill.of-sel{background:var(--accent-soft);border-color:var(--accent);color:var(--accent);font-weight:700;}
.of-seg{display:flex;background:var(--surface-2);border-radius:var(--radius-md);padding:4px;gap:4px;}
.of-seg button{flex:1;border:0;background:none;border-radius:var(--radius-md);padding:10px;min-height:44px;font-size:14px;font-weight:600;color:var(--muted);cursor:pointer;font-family:inherit;}
.of-seg button.of-seg-sel{background:var(--surface);color:var(--ink);font-weight:700;box-shadow:0 1px 4px var(--shadow-color);}
.of-switchrow{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 2px;text-align:left;}
.of-switchrow strong{font-size:15px;font-weight:700;}
.of-switchrow span{display:block;font-size:13px;color:var(--muted);margin-top:2px;}
.of-switch{width:48px;height:28px;flex:none;border-radius:999px;background:var(--switch-track);border:0;cursor:pointer;position:relative;}
.of-switch::after{content:"";position:absolute;top:3px;left:3px;width:22px;height:22px;border-radius:50%;background:#fff;box-shadow:0 1px 4px rgba(0,0,0,.25);transition:left var(--motion-micro) var(--ease-standard);}
.of-switch.of-on{background:var(--accent);}
.of-switch.of-on::after{left:23px;}
/* #511 (user 2026-09-16): marketing opt-in checkbox row on the sign-in step
   (label + native checkbox; mirrors .of-switchrow's copy styling). */
.of-checkrow{display:flex;align-items:flex-start;gap:12px;padding:14px 2px;margin-top:12px;cursor:pointer;text-align:left;}
.of-checkrow input{width:18px;height:18px;flex:none;margin-top:1px;accent-color:var(--accent);cursor:pointer;}
.of-checkrow strong{display:block;font-size:15px;font-weight:700;}
.of-checkrow small{display:block;font-size:13px;color:var(--muted);margin-top:2px;}
/* #663: the .of-optrow/.of-check option-row rules that lived here were
   removed — the onboarding option UI has used .of-pill rows (no check
   circle) since the #506 follow-up design; no markup emits of-optrow. */
.of-infocard{border:1px solid var(--line);background:var(--surface);border-radius:var(--radius-lg);padding:16px;margin-bottom:12px;text-align:left;}
.of-infocard strong{display:block;font-size:15px;font-weight:750;margin-bottom:4px;}
.of-infocard p{font-size:13.5px;line-height:1.5;color:var(--muted);margin:0;}
/* #506 follow-up (user 2026-09-16): "How it works" cards are tappable —
   buttons with an expandable detail card under each (one open at a time). */
button.of-infocard{width:100%;display:block;font-family:inherit;color:var(--ink);cursor:pointer;}
button.of-infocard:active{border-color:var(--accent);}
.of-infocard span{display:block;font-size:13.5px;line-height:1.5;color:var(--muted);}
.of-infocard[aria-expanded="true"]{border-color:var(--accent);}
.of-howdetail{border:1px dashed var(--line);border-radius:var(--radius-md);background:var(--surface);padding:12px 14px;margin:-4px 0 12px;text-align:left;}
.of-howdetail[hidden]{display:none;}
.of-howdetail p{font-size:13.5px;line-height:1.55;color:var(--muted);margin:0;}
/* #506 follow-up: the import screen's plain list of supported sources. */
.of-srclist{list-style:none;width:100%;margin:0 0 8px;padding:0;text-align:left;}
.of-srclist li{position:relative;padding:8px 0 8px 28px;font-size:14.5px;color:var(--muted);}
.of-srclist li::before{content:"✓";position:absolute;left:4px;top:8px;color:var(--accent);font-weight:800;}
/* #506 follow-up: done screen's logo mark + bold app name lockup. */
.of-logo-sm{width:64px;height:64px;border-radius:var(--radius-lg);margin:4px 0 8px;}
.of-sumcard{width:100%;border:1px solid var(--line);background:var(--surface);border-radius:var(--radius-lg);padding:8px 16px;margin-bottom:26px;text-align:left;}
.of-sumcard>div{display:flex;justify-content:space-between;gap:12px;font-size:14px;padding:7px 0;border-bottom:1px solid var(--line);}
.of-sumcard>div:last-child{border-bottom:0;}
.of-sumcard dt{color:var(--muted);font-weight:600;}
.of-sumcard dd{font-weight:700;margin:0;}
.of-codewrap{position:relative;}
.of-codeboxes{display:flex;gap:6px;justify-content:center;margin:8px 0 4px;}
.of-codebox{width:38px;height:56px;border:1px solid var(--line);border-radius:var(--radius-md);background:var(--surface);display:flex;align-items:center;justify-content:center;font-size:22px;font-weight:700;color:var(--ink);}
.of-codebox.of-filled{border-color:var(--accent);}
.of-codebox.of-next{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent);}
/* The single autofill-friendly input sits invisibly over the visual boxes:
   iOS one-time-code autofill only fills one field reliably. */
.of-codeinput{position:absolute;inset:0;width:100%;height:100%;opacity:0;color:transparent;background:transparent;border:0;caret-color:transparent;font-size:16px;}

/* ===== v1.910 Worker A: program-form fixes (#633/#634, user 2026-10-18) =====
   #634: the program form's Progression rules block is a static, always-
   expanded section now (no <details>/<summary>) — the head row keeps the
   old summary's layout without its pointer affordance, and the ⓘ sits
   inline in .settings-title-row next to the heading (#635). */
.progression-head { display: flex; align-items: center; gap: 12px; margin: 0; padding: 2px 0; }
.progression-head .settings-title-row { margin-bottom: 0; }
/* #633 (program-form pair only): #createProgram (primary, 52px) and
   #saveProgramToLibrary (secondary, 44px) are one commit pair — equal
   height (44px, the standard tier; the global .primary-button keeps its
   52px CTA height elsewhere) AND equal width, scoped to their container.
   This supersedes #419's natural-width sizing for this pair only. */
.program-setup-actions .primary-button, .program-setup-actions .secondary-button { flex: 1 1 0; height: 44px; min-height: 44px; }

/* ===== FORK v1.922: consistency polish (fork-only, additive) =====
   Purpose: finish the squared-controls migration the prod directives
   started, unify keyboard focus, and add a paper print sheet.
   Rules applied here (see FORK-ARCHITECTURE.md):
   - Full pills (999px) are reserved for switch tracks, progress bars,
     and true dots. Label chips/tags become squared (--radius-sm) with a
     border on a clean surface instead of text sitting on a grey fill.
   - One focus ring everywhere: :focus-visible gets the same 3px ring
     components already use individually, so keyboard/switch-control
     users see a consistent target on elements that lacked one.
   - Press feedback: primary/secondary buttons compress 1px on :active
     (transform only, disabled under prefers-reduced-motion below).
   - Print: a workout prints as a clean paper sheet (Bear Blog-style:
     ink on white, no chrome, set grid with empty tick boxes) so a
     session can be logged on paper and typed in later. */
.tag, .history-list .set-tag, .set-tag-chip, .tag-list-item,
.streak-chip, .muscle-pill, .workout-context, .trend-change,
.suggestion-inline-kind, .suggestion-name span:not(.suggestion-exercise) {
  border-radius: var(--radius-sm);
}
.tag, .history-list .set-tag, .tag-list-item, .trend-change {
  background: var(--surface); border: 1px solid var(--line);
}
.workout-pr { border-radius: var(--radius-sm); } /* gold PR marker keeps its gold fill, squared shape */
.choice-option span.recommended-badge { border-radius: var(--radius-sm); }
button:focus-visible, a:focus-visible, [tabindex]:focus-visible,
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 3px solid var(--focus-ring); outline-offset: 2px;
}
.primary-button:active, .secondary-button:active, .add-exercise:active {
  transform: translateY(1px);
}
@media (prefers-reduced-motion: reduce) {
  .primary-button:active, .secondary-button:active, .add-exercise:active { transform: none; }
}
@media print {
  /* Paper sheet: hide app chrome, flatten cards to ruled sections. */
  .top-bar, .bottom-nav, .status-scrim, .no-print { display: none !important; }
  body { background: #fff; color: #000; }
  .workout-exercise, .exercise-card, .stat, .history-session {
    border: 1px solid #000; border-radius: 0; box-shadow: none; break-inside: avoid;
  }
  .set-row { border-bottom: 1px solid #999; }
  button { display: none !important; }
}

/* ===== FORK v1.923: plate calculator (fork-only) =====
   Entry button matches the + Add set text-button spec in the utility
   row; the dialog's plate chips are squared (--radius-sm) and the bar
   diagram sizes blocks by denomination via the --pw custom property
   (width in px ≈ denomination, capped so a side fits a phone). */
.plate-open { min-height: var(--btn-h-sm); padding: 0 12px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); cursor: pointer; font-size: 13px; font-weight: 700; }
.set-utility-row { display: flex; gap: 8px; align-items: center; }
.plate-side-label { font-size: 12px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); margin: 14px 0 8px; }
.plate-list { display: flex; flex-wrap: wrap; gap: 6px; }
.plate-chip { padding: 6px 10px; border: 1px solid var(--accent); border-radius: var(--radius-sm); background: var(--accent-soft); color: var(--accent); font-weight: 800; font-variant-numeric: tabular-nums; }
.plate-empty { color: var(--muted); font-weight: 700; }
.plate-diagram { display: flex; align-items: center; gap: 3px; margin: 14px 0 4px; overflow-x: auto; }
.plate-bar { flex: 0 0 34px; height: 6px; background: var(--ink); border-radius: 2px; }
.plate-block { flex: 0 0 auto; width: calc(var(--pw) * 0.9px); max-width: 44px; min-height: 34px; display: grid; place-items: center; border: 1px solid var(--line-strong); border-radius: var(--radius-xs); background: var(--surface-2); font-size: 10px; font-weight: 800; }
.plate-note { font-size: 13.5px; line-height: 1.5; color: var(--muted); }

/* ===== FORK v1.924: audit quick-win sweep (fork-only, additive) =====
   Source: hidden audit UX/UI findings #5/#6/#7/#12 + quick wins 1-7,10.
   Deliberately NOT done here (pinned by prod tests/decisions, recorded
   in IMPROVEMENTS.md): segmented/form-pill heights stay at the pinned
   36px spec; the onboarding import primary stays as the settled call.
   - Remaining grey-pill chips squared + bordered (exercise tags, the
     onboarding tag) so no label sits on a grey fill anywhere.
   - Calendar/info controls squared to --radius-md (visual only; the
     info button's existing 44px hit area is unchanged).
   - Hard-coded radii on chrome (top bar, nav, swipe rows, skeletons)
     pulled onto the token scale.
   - .back stops being a grey slab: surface + border like every other
     secondary button (also gives its hover a visible change).
   - Set placeholders back to a legible 12px (was 10px on small screens).
   - The two :focus-visible rules that suppressed the ring entirely
     (set number, delete-set) now get the standard ring. */
.exercise-tag-chip, .of-tag { border-radius: var(--radius-sm); background: var(--surface); border: 1px solid var(--line); color: var(--ink); }
.calendar-page, .exercise-info-button { border-radius: var(--radius-md); }
.top-bar-btn, .nav-item { border-radius: var(--radius-md); }
.set-swipe { border-radius: var(--radius-md); }
.skel { border-radius: var(--radius-sm); }
.back { background: var(--surface); border: 1px solid var(--line); }
.log-input::placeholder { font-size: 12px; }
.log-set-number:focus-visible, .delete-set-inline:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: 2px; }
.add-set { min-height: 44px; }

/* ===== FORK v1.926/v1.927: rest timer chip + copy-last (fork-only) =====
   The chip is a floating squared card above the bottom nav: one time
   readout, two nudge buttons, one dismiss — nothing else competes with
   the set being logged. Copy-last matches the Plates button spec. */
.copy-last { min-height: var(--btn-h-sm); padding: 0 12px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); cursor: pointer; font-size: 13px; font-weight: 700; }
.fork-rest-chip { position: fixed; left: 50%; transform: translateX(-50%); bottom: calc(96px + env(safe-area-inset-bottom)); z-index: 60; display: flex; align-items: center; gap: 10px; padding: 8px 10px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); box-shadow: var(--shadow); }
.fork-rest-chip.is-done { border-color: var(--accent); background: var(--accent-soft); }
.fork-rest-time { font-size: 22px; font-weight: 800; font-variant-numeric: tabular-nums; min-width: 58px; text-align: center; }
.fork-rest-adj, .fork-rest-dismiss { min-width: 44px; min-height: 44px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); cursor: pointer; font-weight: 800; }

/* ===== FORK v1.929: starter programs (fork-only) =====
   Three squared choice cards in the Program tab's own card spec —
   name, one-line description, whole card is the button. */
.fork-starter-list { display: grid; gap: 10px; margin: 14px 0 10px; }
.fork-starter { display: grid; gap: 2px; text-align: left; padding: 14px 16px; border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface); color: var(--ink); cursor: pointer; font: inherit; }
.fork-starter strong { font-size: 16px; }
.fork-starter span { color: var(--muted); font-size: 13.5px; }
.fork-starter:hover { border-color: var(--accent); }

/* ===== FORK v1.931: experiment gates (fork-only) =====
   When an experiment is switched off in Settings → Fork experiments,
   core/fork-experiments.js puts a fork-off-<key> class on <body> and
   the gated in-card buttons hide. The modules' click handlers also
   check the registry — CSS is the visual gate, the check is the
   functional one. */
body.fork-off-plates [data-plate-open],
body.fork-off-copyLast [data-copy-last] { display: none !important; }

/* ===== FORK v1.933: pedantic UI pass — scroll clearance + labels =====
   Screen-2 root cause family: the page scrolls on <body>, the bottom
   nav is fixed, and clearance came from .shell's fixed 112px bottom
   padding — LESS than the app's own --bottom-nav-clearance token
   (82px + safe-area, ~116px on a notched phone) and with no allowance
   for iOS Safari's expanding browser chrome in non-standalone use.
   The last card on every tab could sit trapped under the nav and the
   scroll appeared to "stick" at it. Clearance now derives from the
   token plus breathing room, on the one container every view shares. */
.shell { padding-bottom: calc(var(--bottom-nav-clearance) + 40px); }
.app { min-height: 100dvh; }
body { min-height: 100dvh; }
/* The starter card is the calmness benchmark; the setup card below it
   gets the same top rhythm instead of crowding it. */
#programSetup { margin-top: 16px; }
/* Floating legends must never truncate ("INCREMENT VAL…"): labels are
   short by copy, and the legend gets a tighter tracking + smaller size
   so the longest remaining labels fit their half-width fields. */
.rule-field > span:first-child { font-size: 11px; letter-spacing: .02em; }
/* Minor 1 (persona): the Batch-1 focus ring was not evident on the
   bottom nav — give nav items an explicit INSET ring (an outset ring
   is clipped by the nav bar's own box). */
.nav-item:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: -3px; }
/* The custom-range disclosure button sits under the preset row. */
.fork-custom-range-btn { margin: 8px 0 4px; }

/* ===== FORK v1.934: Anchor & Preserve (#669) =====
   The set-pattern override row in Exercise options -> Progression,
   laid out like the dumbbell rows it sits beside. */
.prog-pattern-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 10px; }
.prog-pattern-row select { min-height: 36px; padding: 0 8px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface); color: var(--ink); }

/* ===== FORK v1.935: scheme template rows ===== */
.fork-template-row { margin-top: 10px; }
.fork-template-row .settings-subhead { display: block; margin-bottom: 6px; }

/* ===== FORK v1.938: Settings search ===== */
.fork-settings-search { margin-bottom: 14px; }
body.fork-off-settingsSearch .fork-settings-search { display: none; }

/* ===== FORK v1.940: containment + one badge spec + spacing scale =====
   Spacing scale (item 11): the token ladder is now complete and every
   fork rule below uses it — --space-1:4 (hairline gaps inside a control),
   2:8 (chips/inline), 3:12 (field gaps inside a card), 4:16 (card pad /
   section gaps), 5:24 (screen sections), 6:32 (screen-level breaks).
   S1 containment: .workout-meta (shared by the live editor AND the
   saved-workout builder — one component, both hosts) becomes ONE card:
   surface, one border, card padding, 12px internal rhythm. The program
   context reads as the card's header line, not a floating pill.
   One badge spec (S2): .saved-pill and the live context chip share one
   shape — squared, solid 1px border, 11px/800 uppercase; badges differ
   ONLY in colour (accent = primary context, neutral = secondary). The
   dashed outlines are gone. */
:root { --space-1: 4px; --space-6: 32px; }
.workout-meta { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: var(--card-pad); gap: var(--space-3); margin-bottom: var(--space-4); }
.workout-meta .workout-context { display: block; margin: 0; padding: 0 0 var(--space-2); border: 0; border-bottom: 1px solid var(--line); border-radius: 0; background: transparent; color: var(--accent); font-size: 11px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; }
.saved-pill { border: 1px solid var(--accent); border-radius: var(--radius-md); background: var(--accent-soft); }
.saved-pill.program-part-pill { border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); font-size: 11px; font-weight: 800; letter-spacing: .07em; text-transform: uppercase; }
/* ===== FORK v1.941: live elapsed line ===== */
.workout-elapsed { font-size: 12px; font-weight: 700; color: var(--muted); }

/* ===== FORK v1.942: detailed progression panel (mockup UX) =====
   Green-tinted panel for actionable suggestions; muted/dashed for
   holds and notices; a warning callout for clamp/fatigue/stale notes.
   All colours are theme tokens, so the panel works in every theme. */
.suggestion-panel { margin: 10px 0; padding: var(--space-3) var(--card-pad); border: 1px solid var(--accent); border-radius: var(--radius-lg); background: var(--accent-soft); text-align: left; }
.suggestion-panel.is-hold { border-style: dashed; border-color: var(--line); background: var(--surface-2); }
.suggestion-panel.is-notice { border-style: dashed; border-color: var(--line); background: transparent; }
.suggestion-panel.is-applied { border-color: var(--line); background: transparent; opacity: .75; }
.suggestion-panel-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); }
.suggestion-panel-kind { font-size: 13px; font-weight: 800; color: var(--accent); }
.suggestion-panel.is-hold .suggestion-panel-kind, .suggestion-panel.is-notice .suggestion-panel-kind { color: var(--ink); }
.suggestion-panel-chip { padding: 2px 8px; border: 1px solid var(--accent); border-radius: var(--radius-sm); font-size: 10px; font-weight: 800; letter-spacing: .05em; text-transform: uppercase; color: var(--accent); white-space: nowrap; }
.suggestion-panel-chip.is-none { border-color: var(--line); color: var(--muted); }
.suggestion-panel-scheme { margin-top: 2px; font-size: 11px; font-weight: 700; color: var(--muted); }
.suggestion-panel-target { display: block; margin-top: var(--space-2); font-size: 20px; font-weight: 800; color: var(--ink); }
.suggestion-panel-why { margin: var(--space-2) 0 0; font-size: 12px; line-height: 1.45; color: var(--ink); }
.suggestion-panel-last { margin-top: var(--space-2); font-size: 12px; font-weight: 650; color: var(--muted); }
.suggestion-panel .suggestion-sets { margin-top: var(--space-2); }
.suggestion-panel-sets { margin-top: var(--space-2); }
.suggestion-panel-sets summary { font-size: 12px; font-weight: 750; color: var(--accent); cursor: pointer; min-height: 32px; display: flex; align-items: center; }
.suggestion-panel-callout { margin-top: var(--space-2); padding: var(--space-2) var(--space-3); border: 1px solid var(--accent); border-radius: var(--radius-md); background: var(--surface); font-size: 12px; line-height: 1.45; }
.suggestion-panel-callout.is-warning { border-color: var(--rose); color: var(--ink); }
.suggestion-panel-callout.is-stale { border-style: dashed; border-color: var(--line); color: var(--muted); }
.suggestion-panel-apply { width: 100%; margin-top: var(--space-3); min-height: var(--btn-h); border: 1px solid var(--accent); border-radius: var(--radius-md); background: var(--surface); color: var(--accent); font-size: 13px; font-weight: 800; cursor: pointer; }
/* FORK v1.942 (visual adoption pass): panel-by-panel vs the #669
   mockup — TAKEN: the mockup's airy 16px+ panel padding, small-caps
   kind headline, 24px target (the mockup's type scale step between
   card titles and body), hairline-divided per-set lines, and the
   filled warning callout (rose-soft fill + rose border; the mockup's
   red-brown box adapted to the app's rose tokens so it works in every
   theme). ADAPTED: the mockup's pill chip becomes the app's squared
   chip (standing rule: pills are for switches/progress only), and the
   mockup's dark green panel becomes accent-soft/accent tokens (theme
   -safe). REJECTED: the mockup's Today panel copied verbatim as a
   second style — one panel spec with is-hold/is-notice states carries
   both, per the one-component rule. */
.suggestion-panel { padding: var(--space-4); }
.suggestion-panel-kind { font-size: 12px; letter-spacing: .06em; text-transform: uppercase; }
.suggestion-panel-target { font-size: 24px; margin-top: var(--space-3); }
.suggestion-panel .suggestion-set-line { padding: 6px 0; border-bottom: 1px solid var(--line); }
.suggestion-panel .suggestion-set-line:last-child { border-bottom: 0; }
.suggestion-panel-callout.is-warning { background: var(--rose-soft); border-color: var(--rose); }
.suggestion-panel-apply { margin-top: var(--space-4); }

/* ===== FORK v1.943: Simplified navigation (Train tab) =====
   Program nav items hide ONLY under the pre-paint html class; the
   nodes, ids, and wiring stay (desktop tabs proxy the bottom nav).
   The bottom nav is a 5-col grid — under the merge it becomes 4.
   Source chips for the unified Train list: one squared spec, no fill
   (the no-grey-pill rule), colour only via the accent for PROGRAM. */
html.fork-simple-nav #programNav { display: none; }
html.fork-simple-nav .desktop-tab[data-view="program"] { display: none; }
html.fork-simple-nav .bottom-nav { grid-template-columns: repeat(4, 1fr); }
.train-program-head { margin-top: var(--space-5); }
.train-source-chip { display: inline-block; padding: 1px 6px; border: 1px solid var(--line); border-radius: var(--radius-sm); font-size: 10px; font-weight: 800; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }
.train-source-chip.is-program { border-color: var(--accent); color: var(--accent); }

/* ===== FORK v1.944: UI variations =====
   Density (Compact): the spacing TOKENS tighten — card padding and
   section gaps shrink on the same scale; control heights are settled
   specs and deliberately untouched. */
html.fork-density-compact { --space-3: 10px; --space-4: 12px; --space-5: 16px; --space-6: 24px; --card-pad: 12px; }
/* Set rows (Two-field, mockup A): the RPE box leaves the strip and
   the grid re-flows to weight + reps; RPE data is untouched. */
html.fork-setrow-twofield .log-set { grid-template-columns: 44px minmax(0,1fr) minmax(0,1fr) 96px; }
html.fork-setrow-twofield .log-set .rpe-input { display: none; }
html.fork-setrow-twofield .log-labels { grid-template-columns: 44px repeat(2, minmax(0,1fr)) 96px; }
html.fork-setrow-twofield .log-labels span:nth-child(4) { display: none; }
/* Look (Mockup): the mockups' treatments in the current theme's
   tokens — stronger card separation, calmer section rhythm, bolder
   panel hierarchy. No colour tokens change; themes stay as-is. */
html.fork-look-mockup .exercise-card, html.fork-look-mockup .dashboard-card, html.fork-look-mockup .completed-card { box-shadow: var(--shadow-soft); border-color: var(--line); }
html.fork-look-mockup .section-head h2, html.fork-look-mockup .section-head h3 { letter-spacing: -.01em; }
html.fork-look-mockup .suggestion-panel { border-width: 1.5px; }
html.fork-look-mockup .suggestion-panel-target { font-size: 26px; }
html.fork-look-mockup .workout-toolbar { margin-bottom: var(--space-4); }

/* FORK v1.947: superset auto-advance flash — a brief accent outline
   on the partner card after a set completes. */
.fork-superset-flash { outline: 2px solid var(--accent); outline-offset: 2px; transition: outline-color .3s ease; }

/* FORK v1.949: bodyweight card rows — one hairline per entry. */
.fork-bw-row { display: flex; justify-content: space-between; padding: var(--space-2) 0; border-top: 1px solid var(--line); font-variant-numeric: tabular-nums; }
.fork-bw-trend { font-weight: 700; }

/* ===== FORK v1.950: workout header rework v2 =====
   The v1.940 card wrapped three full-height floating-legend fields —
   the user rejected it as "just a box". V2 reworks the fields
   themselves: the name is one title row at the standard input height,
   Focus and Date share ONE row (equal halves), context is a small
   line, times are one small line. Nothing inside the header is
   taller than a standard input row (--input-h). Estimated box:
   ~250px before (context + 3 stacked fields + margins + padding),
   ~150px after (context line + title row + shared row + times line).
   Tokens: padding --space-3/--card-pad, gaps --space-2/3. */
.workout-meta { padding: var(--space-3) var(--card-pad); gap: var(--space-2); }
.workout-meta .field { margin-top: 8px; }
.fork-title-input { font-size: 17px !important; font-weight: 750; }
.fork-meta-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); align-items: start; }
.fork-meta-row .field, .fork-meta-row .workout-focus-disclosure { margin-top: 8px; min-width: 0; }
.workout-meta .workout-focus-summary { min-height: var(--input-h); padding: 0 12px; }
.workout-meta .workout-elapsed { padding-top: 2px; }

/* ===== FORK v1.951: button census fixes + bare-button safety net =====
   The drop-set button shipped (v1.946) with no component class and
   rendered as a default grey pill next to its styled siblings — the
   user caught it in a screenshot. It now shares the utility-row spec
   EXACTLY (same selector group as .plate-open/.copy-last). */
.drop-set-add { min-height: var(--btn-h-sm); padding: 0 12px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); cursor: pointer; font-size: 13px; font-weight: 700; }
/* SAFETY NET (documented as such): any button that reaches the page
   without a component class inherits the standard bordered treatment
   instead of the browser's grey pill, so a missing class can never
   ship that look again. Classed buttons are unaffected (their rules
   are more specific). Fork stylesheet only; upstream buttons all
   carry classes and never hit this rule in practice. */
button:not([class]) { border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); font: inherit; font-size: 13px; font-weight: 700; min-height: var(--btn-h-sm); padding: 0 12px; cursor: pointer; }

/* ===== FORK v1.952: real desktop mode — content uses the width =====
   The inherited #98 desktop mode widened the shell and added a
   site-style header, but every view stayed a single stretched phone
   column. The fork's pass (all scoped to body.desktop, so the phone
   layout is byte-untouched): exercise cards flow two-up in the live
   workout AND the builder, Stats and Program become two-column
   grids with their summary rows spanning full width, and the Train
   mount inside Workout uses the same two-up flow. Composes with the
   density/look <html> variants (token-level) and with Train nav
   (the desktop Program tab already hides under fork-simple-nav). */
body.desktop .workout-exercises { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); align-items: start; }
body.desktop #statsView { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); align-items: start; }
body.desktop #statsView > * { min-width: 0; }
body.desktop #statsView .stats-top-row, body.desktop #statsView #statsGrid, body.desktop #statsView #statsEmpty { grid-column: 1 / -1; }
body.desktop #programView { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); align-items: start; }
body.desktop #programView > * { min-width: 0; }
body.desktop #programView .program-setup, body.desktop #programView .program-cover { grid-column: 1 / -1; }
body.desktop .training-saved #savedWorkoutList { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); }

/* FORK v1.953: builder card actions row — holds the info button that
   moved out of the accordion summary (spec C1). */
.builder-card-actions { display: flex; justify-content: flex-end; margin-bottom: var(--space-2); }
