/*
 * AMS Workout Sync — styling.
 *
 * A sibling of the AMS Instructions app, and deliberately so: the same dark
 * teal ground, the same hand-drawn icons, the same idea that colour tells you
 * where you are. Here the accent comes from the *sport* rather than the tab,
 * because on a training plan the discipline is the thing you are looking for.
 */

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

/*
 * `hidden` has to win. Any class that sets `display` outbids the browser's own
 * [hidden] rule, so an element the code believes it has hidden stays on screen:
 * that is how the status strip kept its space with nothing to say in it, and
 * how a rest day went on offering a "Log this session" button.
 */
[hidden] { display: none !important; }

:root {
    --color-bg: #0a1f1f;
    --color-surface: #1a3535;
    --color-surface-2: #21403f;
    --color-text: #ffffff;
    --color-text-secondary: #a0b8b8;
    --color-border: #2a5555;

    --accent: #00C97B;
    --color-success: #10b981;
    --color-warning: #f59e0b;
    --color-danger: #dc2626;
    --color-danger-text: #ff6b6b;

    /*
     * One colour per discipline: his palette, given as a picture (v1.70.0)
     * and used as given, in both themes. Blue swim, yellow bike, grass-green
     * run (his picture's lime was "a bit too fluorescent" on the screen, so
     * it is the same green with a third less colour in it), orange strength,
     * purple mobility. The same five values paint a bar in
     * the week strip, a dot in the key, the stripe down a card, the badge
     * behind an icon, in daylight and at night.
     *
     * They are bright by design, which is fine for a fill and not for text:
     * this yellow on white is 1.55:1 against the 4.5:1 a label needs. So a
     * sport's colour is never used as text directly. `--sport-ink` below is
     * the same hue with more ink in it (or, at night, more light) and it is
     * derived from the fill rather than kept as a second table, so a colour
     * changed here gets legible text without anybody remembering to.
     */
    --sport-swim: #4f87f8;
    --sport-bike: #ffd60a;      /* v1.72.2: brighter — it read like strength */
    --sport-run: #85ce6c;
    --sport-strength: #ff8c1a;  /* v1.72.2: stronger — it read like the bike */
    --sport-mobility: #8650fe;
    /* A planned row whose sport the app cannot read. Cyan because it is the
       one clear hue the five above leave free. Extras never use it: an extra
       that is not one of the sports is grey (`--sport-rest`). */
    --sport-other: #22d3ee;
    --sport-rest: #64748b;   /* deliberately quiet — a rest day is not a session */
    --sport-race: #ef4444;
    /* Anything done outside the plan has no colour of its own any more: it is
       drawn in its activity's colour (grey for an activity that is not one of
       the sports) with a dotted fill, and the dots are what say "outside the
       plan". See `.week-bar-seg.is-extra`. One pink for every extra (v1.56.0
       to v1.70.0) was his to keep, and he chose the colour being right. */

    /*
     * Text and edges drawn in a sport's colour clamp its lightness (OKLCH) so
     * they can be read on the surface behind them. At night the floor lifts
     * the darker fills (blue, purple) towards the light; the bright ones are
     * already past it and are used as they are.
     */
    --ink-floor: 0.74;
    --ink-ceiling: 1;

    --safe-top: env(safe-area-inset-top, 0px);
    --safe-bottom: env(safe-area-inset-bottom, 0px);
    --tabbar-height: 3.35rem;

    --radius: 14px;
    --radius-sm: 10px;
}

@media (prefers-color-scheme: light) {
    :root:not([data-theme="dark"]) {
        --color-bg: #f6f8f8;
        --color-surface: #ffffff;
        --color-surface-2: #eef3f3;
        --color-text: #16302f;
        --color-text-secondary: #5c7373;
        --color-border: #dce6e6;
        --accent: #00875A;
        /* The fills stay exactly as at night — the palette was given on a
           light ground and is meant to look like that. Only the ink moves:
           on white a label has to be darker than any of these, and 0.52 is
           the lightness at which every one of them clears 5:1 (measured;
           the august audit fails the moment one does not). */
        --ink-floor: 0;
        --ink-ceiling: 0.52;
        --color-danger-text: #c81e1e;
    }
}

/*
 * `--sport` is set inline on a card, a bar or a row and inherited below it;
 * `--sport-ink` is the readable version of the same colour, recomputed on
 * every element from whatever `--sport` it inherits. Declared on `*` on
 * purpose: a custom property is resolved where it is declared, so a single
 * copy on :root would resolve against :root's `--sport` (there is none) and
 * hand every card the accent. The plain rule is the fallback for a browser
 * without relative colour syntax, which gets the fill as its ink — legible at
 * night, and in daylight exactly what the app did before v1.70.0 for a bright
 * colour, which is to say not well.
 */
* { --sport-ink: var(--sport, var(--accent)); }
@supports (color: oklch(from red clamp(0, l, 1) c h)) {
    * { --sport-ink: oklch(from var(--sport, var(--accent)) clamp(var(--ink-floor), l, var(--ink-ceiling)) c h); }
}

html { background: var(--color-bg); }

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    background: var(--color-bg);
    color: var(--color-text);
    height: 100vh;
    height: 100dvh;
    overflow: hidden;
    -webkit-text-size-adjust: 100%;
    -webkit-tap-highlight-color: transparent;
}

.icon-sprite { display: none; }

.icon {
    width: 1.4rem;
    height: 1.4rem;
    display: block;
    color: inherit;
}

/* ---------- screens ---------- */

.screen {
    display: none;
    position: fixed;
    inset: 0;
    flex-direction: column;
    background: var(--color-bg);
}

.screen.active { display: flex; }

.screen-body {
    flex: 1;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 0 1rem calc(var(--tabbar-height) + var(--safe-bottom) + 1.5rem);
}

.screen-footer {
    position: sticky;
    bottom: 0;
    padding: 0.75rem 1rem calc(0.75rem + var(--safe-bottom));
    background: linear-gradient(to top, var(--color-bg) 62%, transparent);
}

/* A detail screen sits above the tab bar, so its body needs less bottom room. */
#workoutScreen .screen-body,
#logScreen .screen-body,
#setupScreen .screen-body { padding-bottom: 1.5rem; }

/* ---------- header ---------- */

.app-header {
    display: flex;
    align-items: flex-end;
    gap: 0.75rem;
    padding: calc(var(--safe-top) + 1.1rem) 1rem 0.85rem;
}

.app-header.detail { align-items: center; }

.app-header-main { flex: 1; min-width: 0; }

.app-eyebrow {
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--accent);
    margin-bottom: 0.15rem;
}

.app-title {
    font-size: 1.6rem;
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1.15;
}

.icon-button {
    flex-shrink: 0;
    width: 2.6rem;
    height: 2.6rem;
    display: grid;
    place-items: center;
    border: 1px solid var(--color-border);
    border-radius: 50%;
    background: var(--color-surface);
    color: var(--color-text);
    cursor: pointer;
}

.icon-button:active { transform: scale(0.94); }
.icon-button.spinning .icon { animation: spin 0.9s linear infinite; }

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

/* ---------- status strip ---------- */

.status-strip {
    margin: 0 1rem 0.75rem;
    padding: 0.6rem 0.8rem;
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    font-size: 0.82rem;
    color: var(--color-text-secondary);
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.status-strip.warn { border-color: var(--color-warning); color: var(--color-warning); }
.status-strip.bad { border-color: var(--color-danger-text); color: var(--color-danger-text); }
.status-strip .icon { width: 1.05rem; height: 1.05rem; flex-shrink: 0; }

/* ---------- cards ---------- */

.card {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    padding: 1rem;
    margin-bottom: 0.85rem;
}

.card-tappable { cursor: pointer; }
.card-tappable:active { transform: scale(0.99); }

/* The workout card wears its discipline's colour down the left edge. */
.workout-card {
    position: relative;
    padding-left: 1.1rem;
    overflow: hidden;
}

.workout-card::before {
    content: '';
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: 5px;
    background: var(--sport, var(--accent));
}

/*
 * Done, and missed, said by the whole card rather than by a pill on it.
 *
 * The pill was already there and was already right — it was just small. Nine
 * cards down a list, finding out which are behind you meant reading nine
 * pills. The border answers that from a glance and the pill keeps the detail.
 *
 * Colour matches the pill exactly (`.pill.done`, `.pill.missed`), so the two
 * marks on one card can never disagree about what they mean. The wash is kept
 * very light: it has to survive being read beside the sport's own colour bar
 * down the left edge, not compete with it.
 */
.workout-card.is-done,
.workout-card.is-missed-card {
    /* 2px of border and 1px of inset ring: three pixels of edge, which is what
       "a bit thicker" turned out to mean when it was drawn. */
    border-width: 2px;
}

.workout-card.is-done {
    border-color: var(--color-success);
    box-shadow: inset 0 0 0 1px var(--color-success);
    background: color-mix(in srgb, var(--color-success) 11%, var(--color-surface));
}

.workout-card.is-missed-card {
    border-color: var(--color-danger-text);
    box-shadow: inset 0 0 0 1px var(--color-danger-text);
    background: color-mix(in srgb, var(--color-danger-text) 11%, var(--color-surface));
}

/*
 * An extra, framed like the two above — but dotted, and in the activity's
 * own colour.
 *
 * Green and red are *states* — done and missed. An extra has no state to
 * report: it is always done, which is why its bar in the week strip is never
 * hollow (v1.56.0). This frame reports a *kind*, and it does so by its line
 * rather than by its colour: dotted, the same mark the bars carry. That is
 * what lets the colour be the activity's — a run logged as an extra is framed
 * in run-green dots, where a planned run he did is framed in the solid
 * success green. Two releases (yellow, then pink) kept the frame off the
 * activity's colour for exactly this case; the dots do that job now.
 *
 * The ink rather than the fill: a 2px dotted line of the bike's yellow on
 * white would not be there. No inset ring — a solid ring inside a dotted
 * border would make it a solid frame again.
 */
.workout-card.is-extra-card {
    border-width: 2px;
    border-style: dotted;
    border-color: var(--sport-ink);
    background: color-mix(in srgb, var(--sport, var(--accent)) 9%, var(--color-surface));
}

/*
 * The tick, in the corner of a session you have done.
 *
 * Filled rather than outlined, and the one solid patch of colour on the card:
 * the border and the pill both report the fact quietly, and this is the part
 * that is allowed to be pleased about it. Drawn in the corner because that is
 * where he drew it.
 */
.done-tick {
    position: absolute;
    top: 0.6rem;
    right: 0.6rem;
    width: 1.75rem;
    height: 1.75rem;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: var(--color-success);
    color: var(--color-surface);
    box-shadow: 0 1px 4px color-mix(in srgb, var(--color-success) 45%, transparent);
}

/*
 * The same tick on an extra, in the activity's colour to match its frame. On
 * an extra run that is a green near the success green of a planned one; the
 * dotted frame beside it is what tells the two cards apart, and the tick is
 * the part that is pleased about the work rather than the part reporting it.
 */
.done-tick.is-extra {
    background: var(--sport-ink);
    box-shadow: 0 1px 4px color-mix(in srgb, var(--sport-ink) 45%, transparent);
}

.done-tick .icon { width: 1rem; height: 1rem; }

/* A long title must wrap before it reaches the tick rather than run under it. */
.workout-card.is-done .workout-card-titles,
.workout-card.is-extra-card .workout-card-titles { padding-right: 1.9rem; }

/* Offered on a card that has shrunk to one line, so it has to sit quietly on
   it rather than reopen the card. Full width would read as a decision to make;
   this is the one thing left to do with a session that is finished. */
.send-done {
    margin-top: 0.55rem;
    border-color: color-mix(in srgb, var(--color-success) 55%, var(--color-border));
    color: var(--color-success);
}

.workout-card-head {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    margin-bottom: 0.5rem;
}

.sport-badge {
    width: 2.4rem;
    height: 2.4rem;
    flex-shrink: 0;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: color-mix(in srgb, var(--sport, var(--accent)) 18%, transparent);
    color: var(--sport-ink);
}

.sport-badge .icon { width: 1.3rem; height: 1.3rem; }

.workout-card-titles { flex: 1; min-width: 0; }

.workout-card-sport {
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--sport-ink);
}

.workout-card-title {
    font-size: 1.02rem;
    font-weight: 650;
    line-height: 1.25;
    overflow-wrap: anywhere;
}

.workout-card-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem 0.5rem;
    margin-top: 0.5rem;
}

.pill {
    font-size: 0.74rem;
    font-weight: 600;
    padding: 0.2rem 0.55rem;
    border-radius: 999px;
    background: var(--color-surface-2);
    color: var(--color-text-secondary);
    border: 1px solid var(--color-border);
    white-space: nowrap;
}

.pill.done { color: var(--color-success); border-color: var(--color-success); }
.pill.pending { color: var(--color-warning); border-color: var(--color-warning); }
.pill.missed { color: var(--color-danger-text); border-color: var(--color-danger-text); }
.pill.strong { color: var(--sport-ink); border-color: var(--sport-ink); }

/* ---------- sections of a workout ---------- */

.section-block {
    border-left: 2px solid var(--color-border);
    padding: 0 0 0 0.85rem;
    margin: 0 0 0.9rem;
    position: relative;
}

.section-block::before {
    content: '';
    position: absolute;
    left: -5px;
    top: 0.35rem;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--sport, var(--accent));
}

.section-label {
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--sport-ink);
    margin-bottom: 0.2rem;
}

.section-text {
    font-size: 0.96rem;
    line-height: 1.5;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.section-target {
    font-size: 0.8rem;
    color: var(--color-text-secondary);
    margin-top: 0.2rem;
}

/* ---------- day headings on the plan ---------- */

.day-heading {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    margin: 1.25rem 0 0.6rem;
}

.day-heading:first-child { margin-top: 0.35rem; }

.day-heading h2 {
    font-size: 0.95rem;
    font-weight: 700;
}

.day-heading span {
    font-size: 0.78rem;
    color: var(--color-text-secondary);
}

.day-heading.is-today h2 { color: var(--accent); }

/* ---------- segmented control ---------- */

.segmented {
    display: flex;
    gap: 0.25rem;
    margin: 0 1rem 0.85rem;
    padding: 0.25rem;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: 999px;
}

.segment {
    flex: 1;
    min-width: 0;
    padding: 0.45rem 0.35rem;
    white-space: nowrap;
    border: none;
    border-radius: 999px;
    background: transparent;
    color: var(--color-text-secondary);
    font-size: 0.82rem;
    font-weight: 600;
    font-family: inherit;
    cursor: pointer;
}

.segment.active { background: var(--accent); color: var(--accent-ink); }

/* ---------- buttons ---------- */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    padding: 0.8rem 1.1rem;
    border-radius: var(--radius-sm);
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-text);
    font-size: 0.95rem;
    font-weight: 600;
    font-family: inherit;
    cursor: pointer;
}

/*
 * A button that says what it does under what it will write.
 *
 * `.btn` is a horizontal flex row, so a two-line label needs the axis turned
 * before either line is placed. The gap is tightened from the row's 0.4rem —
 * at that spacing the two lines read as two separate labels rather than one
 * thing said twice over.
 */
.btn-stack {
    flex-direction: column;
    gap: 0.1rem;
    line-height: 1.25;
}

.btn-stack-main { font-size: 0.95rem; font-weight: 700; }

/* Quiet on purpose. It is there for the first few presses and then it is
   scenery, so it must not compete with the number above it. */
.btn-stack-sub {
    font-size: 0.72rem;
    font-weight: 500;
    opacity: 0.82;
}

.btn:active { transform: scale(0.985); }
.btn:disabled { opacity: 0.5; }

.btn-primary {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-ink);
}

.btn-block { width: 100%; }
.btn-small { padding: 0.5rem 0.75rem; font-size: 0.85rem; }
.btn-danger { color: var(--color-danger-text); border-color: var(--color-danger-text); background: transparent; }

.button-row {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
}

.button-row .btn { flex: 1; min-width: 8rem; }

/* A secondary action in a row alongside a primary one should not compete with
   it for attention — "Missed" is the exception, not the intent. */
.button-row .btn-small { flex: 0 0 auto; min-width: 0; }
.screen-footer .button-row .btn:not(.btn-primary) { flex: 0 0 auto; min-width: 6rem; }

/* ---------- forms ---------- */

.field { margin-bottom: 0.85rem; }

.field label {
    display: block;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--color-text-secondary);
    margin-bottom: 0.3rem;
}

.field-unit {
    font-weight: 500;
    text-transform: none;
    letter-spacing: 0;
    opacity: 0.75;
}

.field input,
.field select,
.field textarea {
    width: 100%;
    padding: 0.7rem 0.75rem;
    font-size: 1rem;
    font-family: inherit;
    color: var(--color-text);
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    -webkit-appearance: none;
    appearance: none;
}

.field select {
    background-image: linear-gradient(45deg, transparent 50%, var(--color-text-secondary) 50%),
                      linear-gradient(135deg, var(--color-text-secondary) 50%, transparent 50%);
    background-position: calc(100% - 1.1rem) 55%, calc(100% - 0.75rem) 55%;
    background-size: 6px 6px, 6px 6px;
    background-repeat: no-repeat;
    padding-right: 2.2rem;
}

.field input:focus,
.field select:focus,
.field textarea:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent);
}

.field textarea { min-height: 5.5rem; resize: vertical; line-height: 1.45; }

.field-hint {
    font-size: 0.76rem;
    color: var(--color-text-secondary);
    margin-top: 0.3rem;
    line-height: 1.4;
}

/*
 * A field you have changed, on a form you came back to correct.
 *
 * The point is to be able to look down the form before pressing Save and see
 * exactly which numbers will be written — the same answer the button gives in
 * words, given again in the place your eye already is. Quiet: a tinted edge
 * and a bolder label, not a banner.
 */
.field.is-changed input,
.field.is-changed textarea {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);
}

.field.is-changed > label {
    color: var(--accent);
}

/* Nothing changed yet, so there is nothing to press. Reads as "not yet"
   rather than as broken. */
#saveLogButton:disabled {
    opacity: 1;
    background: var(--color-surface-2);
    border-color: var(--color-border);
    color: var(--color-text-secondary);
}

.field-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.6rem;
}

/* ---------- settings ---------- */

.settings-group { margin-bottom: 1.5rem; }

.settings-group > h2 {
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--accent);
    margin: 0 0 0.55rem 0.15rem;
}

/* ---------- the folded-away half of Settings ---------- */

/* Set-once and don't-tap-that-by-accident live behind one press. Shut, it is a
   single quiet row; open, it is an ordinary group. */
.settings-fold {
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    padding: 0.15rem 0.75rem 0.3rem;
    background: var(--color-surface);
}

.settings-fold.is-open { padding-bottom: 0.9rem; }

.settings-fold-head {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
    padding: 0.75rem 0;
    border: none;
    background: none;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.settings-fold-text { flex: 1; min-width: 0; }

.settings-fold-title {
    display: block;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--color-text);
}

.settings-fold-sub {
    display: block;
    margin-top: 0.15rem;
    font-size: 0.78rem;
    line-height: 1.35;
    color: var(--color-text-secondary);
}

/* The chevron points the way it is about to go. */
.settings-fold-chevron {
    flex-shrink: 0;
    width: 1.15rem;
    height: 1.15rem;
    color: var(--color-text-secondary);
    transform: rotate(180deg);
    transition: transform 0.2s ease;
}

.settings-fold.is-open .settings-fold-chevron { transform: rotate(270deg); }

.settings-fold-body {
    padding-top: 0.5rem;
}

@media (prefers-reduced-motion: reduce) {
    .settings-fold-chevron { transition: none; }
}

/*
 * The button lines up with the *title*, not with the middle of the row.
 *
 * It used to be centred, which on any row whose description ran to two lines
 * put the button level with the description rather than with the thing it
 * belongs to. Five separate lines in here were reported as misleading, and
 * every one of them was a description sitting shoulder to shoulder with a
 * button: "Every logged session has reached the workbook" beside Sync now,
 * "Nine things you asked for" beside What's new. They were read as labels for
 * the buttons because that is exactly where they were drawn.
 */
/*
 * No rule between rows. Space separates them instead, which is enough — a
 * heading, a gap and a title are already three signals that a new thing has
 * started, and the rule was a fourth nobody had asked for. Asked for
 * directly: "delete all lines in the settings page".
 *
 * The gap has to do the work the rule was doing, so it is larger than the
 * padding that used to sit either side of one.
 */
.settings-row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.55rem 0 0.95rem;
}

.settings-row:last-child { padding-bottom: 0.2rem; }

/* Optically level with the title's cap height rather than its box. */
.settings-row > .btn { margin-top: -0.15rem; }

.settings-row-main { min-width: 0; flex: 1; }

.settings-row-title { font-size: 0.94rem; font-weight: 600; }

.settings-row-sub {
    font-size: 0.79rem;
    color: var(--color-text-secondary);
    /* Tight to the title it belongs to, and a long way from the rule below. */
    margin-top: 0.2rem;
    padding-right: 0.2rem;
    overflow-wrap: anywhere;
    line-height: 1.4;
}

.prose {
    font-size: 0.86rem;
    line-height: 1.55;
    color: var(--color-text-secondary);
}

.prose p { margin-bottom: 0.6rem; }
.prose ol, .prose ul { margin: 0 0 0.6rem 1.1rem; }
.prose li { margin-bottom: 0.3rem; }
.prose code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.82em;
    background: var(--color-surface-2);
    padding: 0.1rem 0.3rem;
    border-radius: 4px;
    overflow-wrap: anywhere;
}
.prose strong { color: var(--color-text); }
.prose em { font-style: normal; color: var(--color-text); opacity: 0.9; }

/* ---------- sheet setup ---------- */

.map-row {
    display: grid;
    grid-template-columns: 1fr 8.5rem;
    gap: 0.6rem;
    align-items: center;
    padding: 0.5rem 0;
    border-bottom: 1px solid var(--color-border);
}

.map-row:last-child { border-bottom: none; }

.map-row-label { font-size: 0.9rem; font-weight: 600; }

.map-row-sub {
    font-size: 0.75rem;
    color: var(--color-text-secondary);
    margin-top: 0.1rem;
}

.map-row select {
    width: 100%;
    padding: 0.5rem 0.5rem;
    font-size: 0.85rem;
    font-family: inherit;
    color: var(--color-text);
    background: var(--color-surface-2);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    -webkit-appearance: none;
    appearance: none;
}

.map-row select:focus { outline: none; border-color: var(--accent); }
.map-row.unmapped select { border-color: color-mix(in srgb, var(--color-warning) 60%, var(--color-border)); }

/* ---------- empty states ---------- */

/*
 * Less air than it had. A rest day is the emptiest this screen ever gets, and
 * two and three quarter rems above and below "nothing scheduled" pushed the
 * one genuinely colourful thing on the page — the sessions coming up — off the
 * bottom of it.
 */
.empty-state {
    text-align: center;
    padding: 1.8rem 1.25rem;
    color: var(--color-text-secondary);
}

/* The border colour made this all but invisible, which on a rest day left a
   screen with nothing on it at all. It is the app's own green now, quietened. */
.empty-state .icon {
    width: 2.6rem;
    height: 2.6rem;
    margin: 0 auto 0.85rem;
    color: var(--accent);
    opacity: 0.7;
}

.empty-state h2 {
    font-size: 1.1rem;
    font-weight: 650;
    color: var(--color-text);
    margin-bottom: 0.4rem;
}

.empty-state p { font-size: 0.88rem; line-height: 1.55; margin-bottom: 1rem; }

/* ---------- tab bar ---------- */

.tab-bar {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    display: flex;
    height: calc(var(--tabbar-height) + var(--safe-bottom));
    padding-bottom: var(--safe-bottom);
    background: color-mix(in srgb, var(--color-surface) 92%, transparent);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    border-top: 1px solid var(--color-border);
    z-index: 40;
}

/* Detail screens cover the tab bar. */
body.detail-open .tab-bar { display: none; }

.tab {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.15rem;
    border: none;
    background: transparent;
    color: var(--color-text-secondary);
    font-family: inherit;
    font-size: 0.66rem;
    font-weight: 600;
    cursor: pointer;
}

.tab.active { color: var(--accent); }
.tab .icon { width: 1.35rem; height: 1.35rem; }

/* ---------- toast ---------- */

.toast {
    position: fixed;
    left: 50%;
    bottom: calc(var(--tabbar-height) + var(--safe-bottom) + 1rem);
    transform: translate(-50%, 1rem);
    max-width: min(24rem, calc(100vw - 2rem));
    padding: 0.7rem 1rem;
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    color: var(--color-text);
    font-size: 0.87rem;
    line-height: 1.4;
    text-align: center;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease, transform 0.2s ease;
    z-index: 90;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28);
}

.toast.show { opacity: 1; transform: translate(-50%, 0); }
.toast.good { border-color: var(--color-success); }
.toast.bad { border-color: var(--color-danger-text); }

/* ---------- misc ---------- */

.spinner {
    width: 1.15rem;
    height: 1.15rem;
    border: 2px solid var(--color-border);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}

.loading-block {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.6rem;
    padding: 2.5rem 1rem;
    color: var(--color-text-secondary);
    font-size: 0.9rem;
}

.hint-inline {
    font-size: 0.78rem;
    color: var(--color-text-secondary);
    margin-top: 0.4rem;
    line-height: 1.45;
}

.file-list { margin-top: 0.5rem; }

.file-option {
    display: block;
    width: 100%;
    text-align: left;
    padding: 0.65rem 0.75rem;
    margin-bottom: 0.4rem;
    background: var(--color-surface-2);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    color: var(--color-text);
    font-family: inherit;
    font-size: 0.88rem;
    cursor: pointer;
}

.file-option.selected { border-color: var(--accent); color: var(--accent); }
.file-option small { display: block; color: var(--color-text-secondary); font-size: 0.75rem; margin-top: 0.15rem; overflow-wrap: anywhere; }

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

/* The redirect URI, offered for copying rather than retyping. Dropbox demands
   an exact match, and a hand-typed URL is where that goes wrong. */
.copy-field {
    display: block;
    width: 100%;
    margin-top: 0.4rem;
    padding: 0.6rem 0.7rem;
    text-align: left;
    background: var(--color-surface-2);
    border: 1px dashed var(--color-border);
    border-radius: var(--radius-sm);
    color: var(--color-text);
    font-family: inherit;
    cursor: pointer;
}

.copy-field:active { border-color: var(--accent); }

.copy-field code {
    display: block;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.8rem;
    overflow-wrap: anywhere;
    -webkit-user-select: all;
    user-select: all;
}

.copy-field-hint {
    display: block;
    margin-top: 0.3rem;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--accent);
}

/* Planned duration, and how the figure being typed compares with it. Shown
   because the sheet computes compliance itself — the app never writes it. */
/* It became a button when it became tappable; it must not start looking like
   one. Same line, same weight, only now it does something. */
.compliance-line {
    display: block;
    width: 100%;
    text-align: left;
    border: 0;
    background: none;
    font: inherit;
    cursor: pointer;
    padding: 0;
    margin-top: 0.45rem;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--color-text-secondary);
}

/*
 * The move screen's one line: pick the day, then press.
 *
 * Half each, so the order is left to right and the green button no longer
 * outweighs the thing you have to touch first. `align-items: end` lines the
 * button up with the input rather than with the input's label, which is the
 * same mistake `.settings-row` was making before v1.45.0 — a control level
 * with a caption reads as belonging to the caption.
 */
.move-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.6rem;
    align-items: end;
}

.move-row .field { margin-bottom: 0; }

/* The label sits above its own half, not across the whole row. */
.move-row .field label { display: block; }

/* Two words on a narrow button wrap rather than shrink the text. */
.move-row .btn {
    min-height: 46px;
    padding-left: 0.6rem;
    padding-right: 0.6rem;
    text-align: center;
    line-height: 1.2;
}

/* ---------- extras: one quiet row on Today ---------- */

/*
 * What was here was a full-width button plus a two-line paragraph — the
 * loudest thing at the bottom of Today, for the thing he does least. It is one
 * heading row now: about 60px where there were 111, and a 34px control where
 * there was a 358px one.
 */
.extras-heading { margin: 1.15rem 0 0.5rem; }

/*
 * `.icon-button` supplies the circle, the border, the grid centring and the
 * press. Only size, colour and placement are new — a second kind of round
 * button would be a second answer to a question this app has already settled.
 *
 * `align-self`, not `align-items` on the row: `.day-heading` is baseline
 * aligned and its title and count must keep sharing that baseline. A circle
 * has no baseline worth aligning anything to. Getting that backwards is the
 * mistake `.settings-row` was making before v1.45.0.
 *
 * Green is `--accent`, and it is the quieter choice on purpose: it is the
 * colour of every other control in this app, so a green + reads as one more
 * ordinary button rather than as something announcing itself. It was pink
 * for one release (v1.65.0, when pink was the extras' colour), on the argument
 * that at 34px only colour could say "add *what*" — but that pink was the
 * app's word for extras as *data*, the bars in the week strip and the row on
 * an opened day, and a button is not data. (Since v1.71.0 extras have no
 * colour of their own at all, which settles it.) The heading two inches away
 * says what it adds; the colour only has to say it can be pressed. It also
 * stops the row disagreeing with itself: the count link beside it was already
 * `--accent`.
 *
 * Not `--color-success`. v1.63.0 and v1.64.0 spent two releases teaching that
 * green plus a tick means "you have done this", and a completion mark on an
 * empty add button would be a lie told in the app's own vocabulary.
 */
.extras-add {
    align-self: center;
    position: relative;
    width: 2.15rem;
    height: 2.15rem;
    margin-left: auto;          /* the right-hand end of the row, as he asked */
    /*
 * The edge is full-strength accent, not a mix faded towards the border
 * colour. Softening it read well in the dark theme and fell to 2.6:1 on
 * white, under the 3:1 an interactive control's own outline needs — and
 * the audit's probe on this button measures its tap target, not its
 * contrast, so nothing here would have caught it. The subtlety is the
 * green; it does not also have to come out of the edge.
 */
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 10%, var(--color-surface));
    color: var(--accent);
}

.extras-add .icon { width: 1rem; height: 1rem; }

/*
 * 34px drawn is under the 44 the august audit enforces, so a transparent halo
 * makes up the difference without moving a visible pixel. This one may grow
 * sideways as well as up, unlike the halos further up this file: it has no
 * neighbour to steal a tap from, with the count link at the other end of the
 * row and only the body's gutter to its right.
 */
.extras-add::after {
    content: '';
    position: absolute;
    top: -8px;
    bottom: -8px;
    left: -8px;
    right: -8px;
}

/* ---------- the guide ---------- */

.guide-section {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    margin-bottom: 0.6rem;
    overflow: hidden;
}

.guide-section > summary {
    padding: 0.85rem 1rem;
    font-size: 0.95rem;
    font-weight: 650;
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: center;
    gap: 0.55rem;
}

.guide-section > summary::-webkit-details-marker { display: none; }

/* A caret that turns, drawn rather than imported. */
.guide-section > summary::before {
    content: '';
    flex-shrink: 0;
    width: 0.42rem;
    height: 0.42rem;
    border-right: 2px solid var(--accent);
    border-bottom: 2px solid var(--accent);
    transform: rotate(-45deg);
    transition: transform 0.15s ease;
}

.guide-section[open] > summary::before { transform: rotate(45deg); }
.guide-section[open] > summary { color: var(--accent); }

.guide-section .prose {
    padding: 0 1rem 0.9rem;
    border-top: 1px solid var(--color-border);
    padding-top: 0.8rem;
}

.guide-section .prose p:last-child { margin-bottom: 0; }

/* ---------- the version log ---------- */

.change-list {
    margin: 0;
    padding-left: 1.05rem;
    font-size: 0.86rem;
    line-height: 1.55;
    color: var(--color-text-secondary);
}

.change-list li { margin-bottom: 0.45rem; }
.change-list li:last-child { margin-bottom: 0; }

/* The changelog is written with a little markdown in it. The list is set in
   the secondary colour, so the sentence carrying the change is lifted to the
   full one rather than made heavier — the same trick the guide uses. */
.change-list strong { color: var(--color-text); }
.change-list em { font-style: normal; color: var(--color-text); opacity: 0.9; }
.change-list code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.82em;
    background: var(--color-surface-2);
    padding: 0.1rem 0.3rem;
    border-radius: 4px;
    overflow-wrap: anywhere;
}

/* ---------- the week at a glance ---------- */

/*
 * The wash: the week itself, painted as ground. The tint's right edge is the
 * present moment, moving across the card from Monday's edge to Sunday's; a
 * glance says "mid-week" before a single number is read. It is a background
 * layer rather than an element, so it can never sit on top of anything, never
 * intercept a tap, and is clipped by the card's own corners for free. The
 * colour is mixed from the accent so both themes get their own version, and a
 * browser too old for color-mix simply shows no wash — nothing breaks.
 */
.week-card {
    padding: 0.85rem 1rem;
    background-image: linear-gradient(90deg,
        color-mix(in srgb, var(--accent) 13%, transparent) 0,
        color-mix(in srgb, var(--accent) 13%, transparent) var(--week-f, 0%),
        transparent calc(var(--week-f, 0%) + 3%));
}

.week-card-head {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    margin-bottom: 0.55rem;
}

/* The label and count are a button because the whole slate is: tapping opens
   the key to the drawing below. It should not look like a button, though —
   the surprise is the point. */
.week-card-head-main {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
    flex: 1;
    min-width: 0;
    padding: 0;
    border: none;
    background: none;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

/*
 * Hit areas, extended without changing what is drawn.
 *
 * Apple's floor for a touch target is 44 points, and three things here sat
 * well under it — the legend toggle at 15px tall was the worst: the most
 * interesting tap on the card, and the hardest to land. A transparent
 * pseudo-element grows what a finger can hit while every visible pixel stays
 * where it was. Each one extends vertically only, because these controls all
 * have neighbours to the side that a wider halo would steal taps from.
 */
.week-card-head-main,
.week-share,
.btn-small,
.segment { position: relative; }

.week-card-head-main::after,
.week-share::after,
.btn-small::after,
.segment::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: -8px;
    bottom: -8px;
}

/* 15px tall needs more reach than the rest to make 44 — and it has to come
   from above, where only the card's own padding sits. Below it, 9px away, the
   day columns are themselves tappable, and a positioned halo paints over
   them: an even split would have quietly stolen the top of every day's tap. */
.week-card-head-main::after { top: -21px; bottom: -8px; }
.week-share::after { top: -8px; bottom: -8px; left: -8px; right: -8px; }

/* Small, quiet, and out of the way of the tap that opens the key. */
.week-share {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.9rem;
    height: 1.9rem;
    margin: -0.3rem -0.35rem -0.3rem 0;
    padding: 0;
    border: none;
    border-radius: 999px;
    background: none;
    color: var(--color-text-secondary);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.week-share .icon { width: 1.05rem; height: 1.05rem; }
.week-share:active { background: var(--color-surface-2); color: var(--accent); }

.week-card-label {
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--accent);
}

.week-card-count {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--color-text-secondary);
}

/* Hollow is what the week asked for, solid is what you did — the same grammar
   the day columns use, and the legend already explains it there. Outlining the
   whole length gives the planned hours a visible extent, so the green is read
   as a share of something rather than as a line of unknown ambition. */
/* The track carries a wash of the accent rather than nothing at all: at the
   start of a week the bar inside it has no width, and an empty outline was the
   greyest thing on the screen at exactly the moment there was least else on
   it. */
.week-bar {
    height: 10px;
    border-radius: 999px;
    border: 1.5px solid color-mix(in srgb, var(--accent) var(--week-bar-edge, 45%), transparent);
    background: color-mix(in srgb, var(--accent) 10%, transparent);
    overflow: hidden;
}

/* On white the same proportion of ink disappears, and the outline has to hold
   its own beside the hollow day columns rather than fading out beneath them. */
@media (prefers-color-scheme: light) {
    :root:not([data-theme="dark"]) { --week-bar-edge: 70%; }
}
:root[data-theme="light"] { --week-bar-edge: 70%; }

.week-bar > span {
    display: block;
    height: 100%;
    border-radius: 999px;
    background: var(--accent);
    transition: width 0.3s ease;
}

/* The week's hours, set apart from the day columns above so the bar is not
   mistaken for part of that chart. A rule across the top does the separating;
   the text and its bar then read as one block. */
/*
 * The statistics live below the wash, not under it. The wash is the whole
 * card's background, which is what lets it never cover or intercept anything
 * — so rather than trying to stop the background partway down a card whose
 * height moves (the key opens, a day expands), this block repaints the plain
 * surface over it. The wash then ends exactly at the grey rule, wherever the
 * rule happens to be that render. Negative margins carry the repaint out to
 * the card's edges and bottom corners, and the padding puts the text back
 * where it was, so nothing on screen moves by a pixel.
 */
.week-total {
    margin: 0.85rem -1rem -0.85rem;
    padding: 0.75rem 1rem 0.85rem;
    border-top: 1px solid var(--color-border);
    background: var(--color-surface);
    border-radius: 0 0 var(--radius) var(--radius);
}

.week-card-figures {
    margin: 0 0 0.4rem;
    font-size: 0.82rem;
    color: var(--color-text-secondary);
}

/* ---------- sessions that were never recorded ---------- */

.outstanding-nudge {
    margin-bottom: 0.85rem;
    border-color: var(--color-warning);
    color: var(--color-warning);
    font-size: 0.87rem;
}

.day-heading.is-outstanding h2 { color: var(--color-warning); }

/* ---------- the sync queue ---------- */

.queue-item {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
}

.queue-item-main { flex: 1; min-width: 0; }
.queue-item .btn { flex-shrink: 0; }

.queue-error {
    margin-top: 0.35rem;
    font-size: 0.78rem;
    line-height: 1.4;
    color: var(--color-danger-text);
}

/* ---------- the week, drawn ---------- */

.week-strip {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 0.25rem;
    margin: 0.7rem 0 0.85rem;
}

.week-day {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.2rem;
    padding: 0.3rem 0.1rem 0.25rem;
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    font-family: inherit;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.week-day.is-open { background: var(--color-surface-2); }

/* Bars grow from the baseline, so a tall day reads as a tall day. */
.week-day-bars {
    display: flex;
    align-items: flex-end;
    justify-content: center;
    gap: 2px;
    width: 100%;
    height: 42px;
}

.week-bar-seg {
    flex: 1;
    max-width: 11px;
    min-height: 4px;
    border-radius: 2.5px;
    background: var(--sport, var(--accent));
}

/*
 * Outstanding work is hollow; recorded work is solid. That difference has to
 * stay legible without reading anything — but hollow used to mean an outline
 * and nothing else, and on a week with nothing logged yet the whole strip was
 * four thin rules on white.
 *
 * So hollow is tinted rather than empty: the sport's own colour at low
 * strength, inside an edge of it. Still plainly not solid, and the week has
 * the colour of the sports in it. The edge is drawn in the sport's *ink*
 * rather than its fill: since v1.70.0 the fills are bright in daylight too,
 * and a 1.5px line of this yellow on white is 1.55:1 — a hollow bike ride
 * would be a pale smudge. The ink is the same hue dark enough to be a line.
 * At night ink and fill are the same colour for every bright sport.
 */
.week-bar-seg.is-todo {
    background: var(--sport, var(--accent));
    background: color-mix(in srgb, var(--sport, var(--accent)) 26%, transparent);
    border: 1.5px solid var(--sport-ink);
}

/*
 * Something done outside the plan: the activity's colour, filled with dots.
 * He drew it — a tinted bar with darker dots on it — and the dots are the
 * whole message: never hollow (an extra is only ever recorded, so there is no
 * outstanding one for hollow to mean), never plain solid (that is a planned
 * session he did). The dots are in the ink, which at night is the fill itself
 * and in daylight the same hue dark enough to be seen on a pale tint, so the
 * pattern reads on white for a yellow ride as well as for a grey hike. One
 * column of dots down the middle of the bar, at his request (v1.71.1): a 4px
 * grid put two or three columns on an 11px bar and read as texture; a single
 * column of slightly larger dots reads as dots. The tile is the bar's full
 * width and 4.5px tall, so the column stays centred whatever the bar's width.
 */
.week-bar-seg.is-extra {
    background-color: color-mix(in srgb, var(--sport, var(--accent)) 40%, var(--color-surface));
    background-image: radial-gradient(circle at 50% 50%, var(--sport-ink) 1.45px, transparent 1.95px);
    background-size: 100% 4.5px;
    background-position: 0 0.5px;
}

.week-bar-seg.is-missed {
    background: repeating-linear-gradient(
        135deg,
        var(--sport, var(--accent)) 0 2px,
        transparent 2px 4px);
    opacity: 0.7;
}

/* A rest day is a flat line rather than an empty column — planned nothing and
   nothing planned are different things. */
.week-rest {
    align-self: flex-end;
    width: 60%;
    height: 3px;
    border-radius: 2px;
    background: var(--sport-rest);
    opacity: 0.7;
}

/* A rest day he went for a walk on keeps both marks: the plan still asked for
   nothing, and he still went. The line gives up its width to the bar rather
   than pushing it out of the column. */
.week-day-bars .week-rest { min-width: 6px; }

.week-day-letter {
    font-size: 0.66rem;
    font-weight: 700;
    color: var(--color-text-secondary);
    line-height: 1;
}

.week-day-date {
    font-size: 0.62rem;
    color: var(--color-text-secondary);
    opacity: 0.75;
    line-height: 1;
}

.week-day.is-past .week-day-letter,
.week-day.is-past .week-day-date { opacity: 0.45; }

.week-day.is-today .week-day-letter {
    color: var(--accent);
}

.week-day.is-today .week-day-date {
    color: var(--accent);
    opacity: 1;
    font-weight: 700;
}

/* ---------- a choice, asked at the bottom of the screen ---------- */

.action-sheet {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: flex;
    align-items: flex-end;
    justify-content: center;
}

.action-sheet-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.45);
    animation: sheet-fade 0.16s ease-out;
}

.action-sheet-card {
    position: relative;
    width: 100%;
    max-width: 30rem;
    margin: 0.6rem;
    margin-bottom: calc(0.6rem + var(--safe-bottom));
    padding: 1rem;
    border-radius: var(--radius);
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.35);
    animation: sheet-rise 0.2s cubic-bezier(0.2, 0.9, 0.3, 1);
}

@keyframes sheet-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes sheet-rise { from { transform: translateY(12px); opacity: 0; } to { transform: none; opacity: 1; } }

@media (prefers-reduced-motion: reduce) {
    .action-sheet-backdrop, .action-sheet-card { animation: none; }
}

.action-sheet-title {
    margin-bottom: 0.75rem;
    font-size: 0.95rem;
    font-weight: 650;
    text-align: center;
}

.action-sheet-actions {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
    /* A fortnight of sessions is a long list; it scrolls rather than pushing
       Cancel off the bottom of the screen. */
    max-height: 60vh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}

/* Stacked, so the count sits under the name rather than beside it. */
.action-sheet-actions .btn {
    width: 100%;
    flex-direction: column;
    gap: 0;
    padding-top: 0.7rem;
    padding-bottom: 0.7rem;
}

.action-sheet-option-sub {
    display: block;
    margin-top: 0.15rem;
    font-size: 0.78rem;
    font-weight: 500;
    opacity: 0.75;
}

.action-sheet-cancel { color: var(--color-text-secondary); }

/* ---------- the key to the drawing ---------- */

.week-legend-cue {
    display: inline-block;
    margin-left: 0.15rem;
    font-size: 0.8em;
    letter-spacing: 0;
    opacity: 0.35;
}

.week-card-head[aria-expanded="true"] .week-legend-cue { opacity: 1; }

.week-legend {
    padding: 0.65rem 0.75rem;
    margin-bottom: 0.8rem;
    background: var(--color-surface-2);
    border-radius: var(--radius-sm);
    animation: legend-in 0.18s ease-out;
}

@keyframes legend-in {
    from { opacity: 0; transform: translateY(-3px); }
    to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
    .week-legend { animation: none; }
}

.week-legend-title {
    font-size: 0.74rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-text-secondary);
    margin-bottom: 0.5rem;
}

.week-legend-shapes,
.week-legend-sports {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.35rem 0.6rem;
    list-style: none;
}

.week-legend-shapes > li,
.week-legend-sports > li {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    font-size: 0.78rem;
    color: var(--color-text-secondary);
    min-width: 0;
}

/* The swatches are the real thing, not a drawing of it: the same classes the
   strip uses, so the key cannot drift away from what it explains. */
.week-legend-swatch {
    flex-shrink: 0;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    width: 12px;
    height: 16px;
}

.week-legend-swatch .week-bar-seg {
    flex: none;
    width: 9px;
    height: 100%;
    --sport: var(--color-text);
}

.week-legend-swatch .week-rest {
    align-self: flex-end;
    width: 12px;
}

.week-legend-sports {
    margin-top: 0.55rem;
    padding-top: 0.55rem;
    border-top: 1px solid var(--color-border);
}

.week-legend-dot {
    flex-shrink: 0;
    width: 9px;
    height: 9px;
    border-radius: 999px;
    background: var(--sport, var(--accent));
}

.week-legend-note {
    margin-top: 0.55rem;
    font-size: 0.74rem;
    line-height: 1.45;
    color: var(--color-text-secondary);
    opacity: 0.85;
}

/* ---------- a day opened from the strip ---------- */

.week-expanded {
    padding: 0.6rem 0.7rem;
    margin-bottom: 0.8rem;
    background: var(--color-surface-2);
    border-radius: var(--radius-sm);
}

.week-expanded-title {
    font-size: 0.74rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-text-secondary);
    margin-bottom: 0.45rem;
}

.week-expanded-row {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    padding: 0.25rem 0;
    border-left: 3px solid var(--sport, var(--accent));
    padding-left: 0.5rem;
    margin-bottom: 0.3rem;
}

.week-expanded-sport {
    flex-shrink: 0;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--sport-ink);
    min-width: 4.4rem;
}

.week-expanded-what {
    flex: 1;
    min-width: 0;
    font-size: 0.82rem;
    line-height: 1.35;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}

/* The row keeps the activity's colour and says "extra" the way the bar does:
   its edge is dotted. */
.week-expanded-row.is-extra { border-left-style: dotted; }

.week-expanded-meta {
    flex-shrink: 0;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--color-text-secondary);
}

/* ---------- the sync button as the status ---------- */

.sync-button {
    position: relative;
    transition: color 0.2s ease, border-color 0.2s ease;
}

/* Green when everything is through — stated once, on the control that means it,
   rather than in a banner repeating the button beneath it. */
.sync-button.is-synced {
    color: var(--color-success);
    border-color: color-mix(in srgb, var(--color-success) 55%, var(--color-border));
}

.sync-button.is-pending {
    color: var(--color-warning);
    border-color: var(--color-warning);
}

.sync-button.is-error {
    color: var(--color-danger-text);
    border-color: var(--color-danger-text);
}

.sync-button.is-stale {
    color: var(--color-warning);
    border-color: color-mix(in srgb, var(--color-warning) 55%, var(--color-border));
}

/* Not connected is not a fault, so it stays neutral rather than alarming. */
.sync-button.is-local {
    color: var(--color-text-secondary);
    border-color: var(--color-border);
}

.sync-button.is-busy { color: var(--accent); border-color: var(--accent); }

.sync-badge {
    position: absolute;
    top: -3px;
    right: -3px;
    min-width: 1.05rem;
    height: 1.05rem;
    padding: 0 0.22rem;
    display: grid;
    place-items: center;
    border-radius: 999px;
    background: var(--color-warning);
    color: #241a00;
    font-size: 0.66rem;
    font-weight: 800;
    line-height: 1;
    border: 2px solid var(--color-bg);
}

/* The heading is now a phase name rather than a date, so it needs room to
   breathe without shouting. */
#todayScreen .app-title {
    font-size: 1.42rem;
    line-height: 1.2;
}

/* ---------- one colour per tab ---------- */

/*
 * The tab you are in colours the whole screen — every accent, heading, bar and
 * primary button — so the interface itself tells you where you are before you
 * read a word of it. Detail screens deliberately keep the colour of the tab
 * they were opened from, which is a thread back to where you came from.
 *
 * Sport colours are untouched by this: a swim is cyan on every screen.
 */
:root {
    --tab-today: #00C97B;
    --tab-plan: #3B9EFF;
    --tab-progress: #C084FC;
    --tab-settings: #94A3B8;

    /* Text that sits on top of a filled accent. */
    --accent-ink: #04201a;
}

body[data-tab="today"]    { --accent: var(--tab-today);    --accent-ink: #04201a; }
body[data-tab="plan"]     { --accent: var(--tab-plan);     --accent-ink: #04182b; }
body[data-tab="progress"] { --accent: var(--tab-progress); --accent-ink: #1c0a2b; }
body[data-tab="settings"] { --accent: var(--tab-settings); --accent-ink: #16202a; }

@media (prefers-color-scheme: light) {
    :root:not([data-theme="dark"]) {
        --tab-today: #00875A;
        --tab-plan: #1266C8;
        --tab-progress: #7C3AED;
        --tab-settings: #5A6B7B;
        --accent-ink: #ffffff;
    }
    :root:not([data-theme="dark"]) body[data-tab="today"],
    :root:not([data-theme="dark"]) body[data-tab="plan"],
    :root:not([data-theme="dark"]) body[data-tab="progress"],
    :root:not([data-theme="dark"]) body[data-tab="settings"] { --accent-ink: #ffffff; }
}

/* The sync badge is amber whatever the tab, so it keeps its own dark ink. */

/* Each tab wears its own colour when active, and grows a bar above it, so the
   bottom of the screen reads as four coloured sections rather than one. */
.tab { position: relative; }

.tab::before {
    content: '';
    position: absolute;
    top: 0;
    left: 22%;
    right: 22%;
    height: 3px;
    border-radius: 0 0 3px 3px;
    background: transparent;
    transition: background 0.18s ease;
}

.tab[data-tab="today"].active    { color: var(--tab-today); }
.tab[data-tab="plan"].active     { color: var(--tab-plan); }
.tab[data-tab="settings"].active { color: var(--tab-settings); }

.tab[data-tab="today"].active::before    { background: var(--tab-today); }
.tab[data-tab="plan"].active::before     { background: var(--tab-plan); }
.tab[data-tab="settings"].active::before { background: var(--tab-settings); }

/* ---------- the activity list ---------- */

.activity-row {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.6rem 0.15rem;
    border-bottom: 1px solid var(--color-border);
}

.activity-row:last-child { border-bottom: none; }

.activity-dot {
    flex-shrink: 0;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--sport, var(--accent));
}

.activity-main { flex: 1; min-width: 0; }
.activity-label { font-size: 0.94rem; font-weight: 600; }

.activity-kind {
    font-size: 0.72rem;
    color: var(--color-text-secondary);
    margin-top: 0.1rem;
}

.activity-move,
.activity-delete {
    flex-shrink: 0;
    width: 2rem;
    height: 2rem;
    display: grid;
    place-items: center;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface-2);
    color: var(--color-text-secondary);
    font-family: inherit;
    font-size: 0.7rem;
    cursor: pointer;
}

.activity-move:disabled { opacity: 0.3; }
.activity-move:active:not(:disabled) { border-color: var(--accent); color: var(--accent); }

.activity-delete {
    font-size: 1.1rem;
    color: var(--color-danger-text);
    border-color: transparent;
    background: transparent;
}

/* ---------- progress ---------- */

.stat-block {
    background: var(--color-surface);
    border-radius: var(--radius);
    padding: 1rem 1rem 1.1rem;
    margin-bottom: 0.75rem;
}
.stat-title {
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--color-text-secondary);
    margin: 0 0 0.5rem;
}
.stat-lead {
    margin: 0 0 0.9rem;
    font-size: 0.95rem;
    line-height: 1.45;
    color: var(--color-text);
}
.stat-lead strong { font-weight: 650; }
.stat-note {
    margin: 0.85rem 0 0;
    font-size: 0.76rem;
    line-height: 1.5;
    color: var(--color-text-secondary);
}
.stat-note em { font-style: normal; color: var(--color-text); }
.stat-footnote {
    margin: 0.4rem 0.2rem 0;
    font-size: 0.74rem;
    line-height: 1.55;
    color: var(--color-text-secondary);
}

/* A caution that has to be read before the numbers under it, not after. */
.stat-caution {
    background: var(--color-surface-2);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius-sm);
    padding: 0.75rem 0.85rem;
    margin-bottom: 0.75rem;
}
.stat-caution p { margin: 0; font-size: 0.82rem; line-height: 1.5; color: var(--color-text-secondary); }
.stat-caution strong { color: var(--color-text); }

.stat-figures { display: flex; gap: 0.5rem; }
.stat-figure {
    flex: 1;
    background: var(--color-surface-2);
    border-radius: var(--radius-sm);
    padding: 0.6rem 0.4rem;
    text-align: center;
}
.stat-figure-value {
    display: block;
    font-size: 1.3rem;
    font-weight: 650;
    line-height: 1.1;
    color: var(--color-text);
    font-variant-numeric: tabular-nums;
}
.stat-figure-label {
    display: block;
    margin-top: 0.2rem;
    font-size: 0.68rem;
    line-height: 1.25;
    color: var(--color-text-secondary);
}


.stat-rows { display: flex; flex-direction: column; gap: 0.5rem; }
.stat-row { display: flex; align-items: center; gap: 0.5rem; }
.stat-row-dot {
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--sport); flex: none;
}
.stat-row-label { font-size: 0.82rem; color: var(--color-text); width: 4.6rem; flex: none; }
.stat-row-bar {
    flex: 1; height: 7px;
    background: var(--color-surface-2);
    border-radius: 4px; overflow: hidden;
}
.stat-row-fill { display: block; height: 100%; background: var(--sport); border-radius: 4px; }
.stat-row-value {
    font-size: 0.78rem; font-variant-numeric: tabular-nums;
    color: var(--color-text); width: 2.6rem; text-align: right; flex: none;
}
.stat-row-detail {
    font-size: 0.72rem; font-variant-numeric: tabular-nums;
    color: var(--color-text-secondary); width: 2.8rem; text-align: right; flex: none;
}

/* ============================ PHOTOGRAPHS ============================ */

/*
 * A row that scrolls sideways rather than a grid that grows downwards. The
 * session's own words are the point of the screen; the pictures sit under them
 * at a fixed height, however many there are, and the next one showing at the
 * edge is what says to keep going.
 */
.photo-card {
    margin-top: 0.8rem;
}

/*
 * Wraps rather than scrolls. A sideways strip hides whatever does not fit,
 * and what does not fit first is the button on the end — so the row that grew
 * would have taken Add off the screen with it. Three to a row on a phone,
 * nothing hidden, and Add stays where the next picture would go.
 */
.photo-strip {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    padding: 0.15rem 0 0.35rem;
}

.photo-thumb {
    flex: 0 0 auto;
    width: 5.4rem;
    height: 5.4rem;
    padding: 0;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface-2);
    overflow: hidden;
    cursor: pointer;
}

/*
 * The same thumbnail at two thirds the size, for a card in a list rather than
 * the session's own screen. He asked to see the picture there and said it
 * could be smaller than the existing ones; 3.4rem is 54px, under the 86px of
 * the strip and still over the 44px the audit wants under a thumb, so it needs
 * no halo of its own.
 *
 * No wrap. The strip on a session screen wraps because that screen is where
 * every picture belongs; a card in a list gets one row and a "+3", or a
 * session photographed all morning would set the height of a row in the Plan.
 */
.photo-marks {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin-top: 0.7rem;
}

.photo-thumb.is-mark {
    width: 3.4rem;
    height: 3.4rem;
}

/*
 * Sized and bordered like the tiles it follows so the row reads as one run of
 * pictures, but flat and unlit: it is a count, and nothing about it should
 * suggest a fifth photograph is behind it.
 */
.photo-more {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    width: 3.4rem;
    height: 3.4rem;
    border: 1px dashed var(--color-border);
    border-radius: var(--radius-sm);
    color: var(--color-text-secondary);
    font-size: 0.85rem;
    font-variant-numeric: tabular-nums;
}

.photo-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/*
 * The same size as a thumbnail rather than a button below the row, so the
 * place a photo goes and the place they already are read as one thing.
 */
.photo-add {
    flex: 0 0 auto;
    width: 5.4rem;
    height: 5.4rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.1rem;
    border: 1px dashed var(--color-border);
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-text-secondary);
    cursor: pointer;
}

.photo-add-mark {
    font-size: 1.35rem;
    line-height: 1;
}

.photo-add-text {
    font-size: 0.68rem;
    text-align: center;
    padding: 0 0.2rem;
}

/* ---- one photo, full size ---- */

.photo-viewer {
    position: fixed;
    inset: 0;
    z-index: 70;
    display: flex;
    align-items: center;
    justify-content: center;
}

.photo-viewer[hidden] { display: none; }

.photo-viewer-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.82);
}

.photo-viewer-card {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    align-items: center;
    width: 100%;
    max-width: 34rem;
    padding: 0.8rem;
    padding-bottom: calc(0.8rem + var(--safe-bottom));
}

.photo-viewer-card img {
    max-width: 100%;
    /* Room left for the caption and the three buttons under it, so a tall
       photograph does not push them off the bottom of the phone. */
    max-height: 68vh;
    border-radius: var(--radius);
    display: block;
}

.photo-viewer-caption {
    margin: 0;
    font-size: 0.78rem;
    color: #cbd8d8;
    text-align: center;
}

/* The tab bar sits above everything else and would otherwise show through
   the backdrop at the foot of the screen. */
body.is-viewing-photo .tab-bar { display: none; }

/* A button that reads as a link, for "see all" beside a heading. */
.link-button {
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    color: var(--accent);
    text-decoration: underline;
    cursor: pointer;
}

/*
 * A photograph waiting on the new-extra form. It has nothing to be attached to
 * yet, so tapping it takes it off again rather than opening it — the cross
 * says which of the two this is.
 */
.photo-thumb.is-held {
    position: relative;
    border-style: solid;
}

.photo-thumb-x {
    position: absolute;
    top: 0.15rem;
    right: 0.15rem;
    width: 1.15rem;
    height: 1.15rem;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.62);
    color: #fff;
    font-size: 0.85rem;
    line-height: 1;
}

/* An extra's card carries its photo strip inside it, so the strip's own card
   framing would be a box inside a box. */
.workout-card .photo-card {
    background: none;
    border: 0;
    padding: 0;
    margin: 0.7rem 0 0;
    box-shadow: none;
}

/*
 * A short answer and the room beside it.
 *
 * A field that takes one or two characters does not need the width of the
 * screen, and giving it that width leaves a stripe of nothing next to every
 * number. The box is cut to what goes in it and the rest of the line explains
 * what the number means — which the placeholder used to do, badly, since a
 * placeholder disappears exactly when the answer arrives.
 */
.field-split {
    display: flex;
    gap: 0.65rem;
    align-items: center;
}

.field-split input {
    flex: 0 0 5.6rem;
    min-width: 0;
    text-align: center;
}

/*
 * A block, not a flex container. As a flex container each <strong> and each
 * run of text became its own anonymous item, and flex strips the whitespace
 * around those — so "<strong>1</strong> easy" rendered as "1easy". The
 * centring it was there for belongs to the parent anyway.
 */
.field-aside {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    font-size: 0.8rem;
    line-height: 1.35;
    color: var(--color-text-secondary);
}

.field-aside strong { color: var(--color-text); font-weight: 700; }

/*
 * A question mark that costs nothing until it is wanted. Round, quiet, and
 * sized like the buttons beside it so the row still reads as one row.
 */
.help-dot {
    flex: 0 0 auto;
    width: 2rem;
    height: 2rem;
    border-radius: 50%;
    border: 1px solid var(--color-border);
    background: transparent;
    color: var(--color-text-secondary);
    font-size: 0.9rem;
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
}

.help-dot:active { border-color: var(--accent); color: var(--accent); }

/* The sheet explaining itself rather than asking something. */
.action-sheet-note {
    margin: 0 0 0.9rem;
    font-size: 0.86rem;
    line-height: 1.5;
    color: var(--color-text-secondary);
    max-height: 60vh;
    overflow-y: auto;
}

.action-sheet-note p { margin: 0 0 0.6rem; }
.action-sheet-note p:last-child { margin-bottom: 0; }
.action-sheet-note strong { color: var(--color-text); }
.action-sheet-note code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.85em;
    background: var(--color-surface-2);
    padding: 0.1rem 0.3rem;
    border-radius: 4px;
}

/* Inside the Workbook group now, so it should not look like a card of its own. */
.settings-group .settings-fold {
    margin: 0.9rem -0.2rem 0;
    background: none;
    border: 0;
    padding: 0;
}

/* The effort scale, all ten, each one an answer as well as an explanation. */
.rpe-scale {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    margin: 0.2rem 0 0.9rem;
}

.rpe-row {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    width: 100%;
    padding: 0.45rem 0.5rem;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: var(--color-surface-2);
    color: var(--color-text-secondary);
    font: inherit;
    font-size: 0.82rem;
    line-height: 1.35;
    text-align: left;
    cursor: pointer;
}

.rpe-row.is-current { border-color: var(--accent); color: var(--color-text); }
.rpe-row:active { border-color: var(--accent); }

.rpe-row-n {
    flex: 0 0 1.5rem;
    text-align: center;
    font-weight: 700;
    color: var(--color-text);
}

.rpe-row-text { flex: 1 1 auto; min-width: 0; }

/* ====================== THE BLOCK AT A GLANCE ====================== */

/*
 * Eight weeks, stacked. The same alphabet as the week strip on Today — a
 * column per day, a bar per session, height for length, colour for sport,
 * solid for done and hollow for still to come — so there is nothing new to
 * learn, only more of it at once.
 */
.block-card {
    padding: 0.85rem 0.9rem 0.7rem;
}

.block-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.6rem;
    margin-bottom: 0.6rem;
}

.block-title {
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--accent);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.block-sub {
    flex: 0 0 auto;
    font-size: 0.74rem;
    color: var(--color-text-secondary);
}

.block-weeks { display: flex; flex-direction: column; gap: 0.15rem; }

.block-week {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.18rem 0.3rem;
    border-radius: var(--radius-sm);
}

/* This week is the one you are standing in, so it is the one lit. The plain
   colour first, for anything that cannot mix one. */
.block-week.is-now {
    background: var(--color-surface-2);
    background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.block-week.is-past { opacity: 0.55; }

.block-week-label {
    flex: 0 0 4.1rem;
    font-size: 0.7rem;
    color: var(--color-text-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.block-week.is-now .block-week-label { color: var(--color-text); font-weight: 600; }

.block-week-days {
    flex: 1 1 auto;
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 3px;
    height: 2rem;
}

/* The weekday letters, on a row of their own above the weeks. */
.block-letters { padding-bottom: 0; }

.block-letters .block-week-days {
    height: auto;
    align-items: center;
}

.block-letters .block-week-days span {
    text-align: center;
    font-size: 0.62rem;
    letter-spacing: 0.04em;
    color: var(--color-text-secondary);
    opacity: 0.75;
}

.block-day {
    display: flex;
    align-items: flex-end;
    justify-content: center;
    gap: 1px;
    height: 100%;
    border-radius: 2px;
}

.block-day.is-today {
    background: var(--color-surface-2);
    background: color-mix(in srgb, var(--accent) 18%, transparent);
}

/* A rest day is a flat line rather than an empty column: planned nothing and
   nothing planned are different things. Same rule as the week strip. */
.block-rest {
    width: 60%;
    height: 2px;
    align-self: center;
    border-radius: 1px;
    background: var(--color-border);
}

.block-bar {
    flex: 1 1 auto;
    min-width: 3px;
    max-width: 7px;
    border-radius: 2px;
    background: var(--sport, var(--accent));
}

/* Still to do is drawn hollow, exactly as on Today — tinted rather than
   empty, for the same reason. */
.block-bar.is-todo {
    background: color-mix(in srgb, var(--sport, var(--accent)) 26%, transparent);
    box-shadow: inset 0 0 0 1.5px var(--sport-ink);
}

.block-bar.is-missed { background: var(--sport, var(--accent)); opacity: 0.35; }

/* Outside the plan, dotted as on Today and for the same reason: one column
   of dots down the middle. These bars are 3–7px wide, so the dots are smaller
   and closer; at this size it is a stitched line rather than dots, which is
   still not solid and still not hollow. */
.block-bar.is-extra {
    background-color: color-mix(in srgb, var(--sport, var(--accent)) 40%, var(--color-surface));
    background-image: radial-gradient(circle at 50% 50%, var(--sport-ink) 0.95px, transparent 1.35px);
    background-size: 100% 3px;
    background-position: 0 0.5px;
    box-shadow: none;
}

.block-week-hours {
    flex: 0 0 2.8rem;
    text-align: right;
    font-size: 0.7rem;
    color: var(--color-text-secondary);
    white-space: nowrap;
}

.block-week.is-now .block-week-hours { color: var(--color-text); }

.block-foot {
    margin: 0.55rem 0 0;
    font-size: 0.72rem;
    line-height: 1.35;
    color: var(--color-text-secondary);
}

/* ========================== SAYING IT ========================== */

/*
 * The box is the feature, not the microphone beside it. The keyboard's own
 * dictation types into this on any phone there is; the button only saves
 * opening the keyboard, on the browsers that have a recogniser of their own.
 */
.say-card {
    margin-bottom: 0.8rem;
}

.say-row {
    display: flex;
    gap: 0.5rem;
    align-items: stretch;
    margin-bottom: 0.5rem;
}

.say-row input {
    flex: 1 1 auto;
    min-width: 0;
}

.say-mic {
    flex: 0 0 auto;
    width: 2.9rem;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface-2);
    color: var(--color-text);
    cursor: pointer;
}

.say-mic .icon { width: 1.25rem; height: 1.25rem; }

.say-mic.is-listening {
    border-color: var(--accent);
    color: var(--accent);
    animation: say-pulse 1.1s ease-in-out infinite;
}

@keyframes say-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.45; }
}

@media (prefers-reduced-motion: reduce) {
    .say-mic.is-listening { animation: none; }
}

/* The list of words "Say it" knows, printed from the parser's own tables. */
.say-words {
    margin: 0 0 0.7rem;
    padding: 0.5rem 0.6rem;
    background: var(--color-surface-2);
    border-radius: var(--radius-sm);
}

.say-words-name {
    margin: 0 0 0.3rem;
    font-size: 0.8rem;
    font-weight: 700;
    color: var(--color-text);
}

.say-words-list {
    margin: 0;
    line-height: 1.9;
}

.say-words-list code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.72rem;
    background: var(--color-surface);
    padding: 0.1rem 0.32rem;
    border-radius: 4px;
    white-space: nowrap;
}

.say-words-eg {
    margin: 0.4rem 0 0;
    font-size: 0.76rem;
    font-style: italic;
    color: var(--color-text-secondary);
}

/* ======================== THE ROAD TO THE RACE ======================== */

/*
 * Eleven months on one screen. The countdown is the biggest thing because it
 * is the thing you look for; the bar under it is the plan's own shape, each
 * phase as wide as the days it covers — which is why a taper looks short.
 */
.road-card {
    padding: 1rem 0.9rem 0.85rem;
}

.road-head {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    margin-bottom: 0.15rem;
}

.road-count {
    font-size: 2.6rem;
    font-weight: 800;
    line-height: 1;
    letter-spacing: -0.02em;
    color: var(--color-text);
}

.road-unit {
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--color-text-secondary);
}

.road-race {
    margin: 0.35rem 0 0;
    font-size: 1rem;
    font-weight: 700;
    color: var(--color-text);
}

.road-when {
    margin: 0.1rem 0 0;
    font-size: 0.8rem;
    color: var(--color-text-secondary);
}

.road-bar {
    position: relative;
    display: flex;
    margin: 0.9rem 0 0.5rem;
    height: 1.55rem;
    border-radius: var(--radius-sm);
    overflow: hidden;
    background: var(--color-surface-2);
}

/*
 * Four tints of the app's own green rather than four colours: the bands are
 * chapters of one thing, and giving each its own hue would say they were four
 * different things.
 */
.road-band {
    position: relative;
    min-width: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    background: color-mix(in srgb, var(--accent) calc(14% + var(--band) * 9%), transparent);
    border-right: 1px solid var(--color-bg);
}

.road-band:last-child { border-right: 0; }
.road-band.is-done { opacity: 0.55; }
.road-band.is-now { background: color-mix(in srgb, var(--accent) 62%, transparent); }

.road-band-name {
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: clip;
    padding: 0 0.2rem;
    color: var(--color-text);
    opacity: 0.75;
}

.road-band.is-now .road-band-name { opacity: 1; }

/* Where today falls along the whole road. */
.road-now {
    position: absolute;
    top: -2px;
    bottom: -2px;
    width: 2px;
    margin-left: -1px;
    background: var(--color-text);
    box-shadow: 0 0 0 1px var(--color-bg);
}

.road-phase {
    margin: 0 0 0.75rem;
    font-size: 0.8rem;
    color: var(--color-text-secondary);
}

.road-phase strong { color: var(--color-text); }

.road-figures {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.5rem;
    padding-top: 0.75rem;
    border-top: 1px solid var(--color-border);
}

.road-figure { min-width: 0; }

.road-figure-n {
    display: block;
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--color-text);
}

.road-figure-l {
    display: block;
    margin-top: 0.1rem;
    font-size: 0.7rem;
    line-height: 1.3;
    color: var(--color-text-secondary);
}

.road-note {
    margin: 0.7rem 0 0;
    font-size: 0.76rem;
    color: var(--color-warning);
}

/* ========================= IS IT WORKING ========================= */

.trend {
    padding: 0.85rem 0 0;
    border-top: 1px solid var(--color-border);
    margin-top: 0.85rem;
}

.trend-sport {
    margin: 0 0 0.55rem;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--color-text-secondary);
}

/* Then, an arrow, now. The two halves are deliberately identical in weight —
   the comparison is the point, not either number on its own. */
.trend-pair {
    display: flex;
    align-items: center;
    gap: 0.7rem;
}

.trend-half {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.trend-when {
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--color-text-secondary);
}

.trend-pace {
    font-size: 1.25rem;
    font-weight: 700;
    line-height: 1.15;
    color: var(--color-text);
}

.trend-hr {
    font-size: 0.75rem;
    color: var(--color-text-secondary);
}

.trend-arrow {
    flex: 0 0 auto;
    font-size: 1.35rem;
    line-height: 1;
    color: var(--color-text-secondary);
}

.trend-arrow.is-better { color: var(--color-success); }
.trend-arrow.is-down { color: var(--color-warning); }

.trend-verdict {
    margin: 0.5rem 0 0;
    font-size: 0.8rem;
    line-height: 1.4;
    font-weight: 600;
    color: var(--color-text-secondary);
}

.trend-verdict.is-better { color: var(--color-success); }
.trend-verdict.is-down { color: var(--color-warning); }

.trend-basis {
    margin: 0.25rem 0 0;
    font-size: 0.73rem;
    line-height: 1.4;
    color: var(--color-text-secondary);
}

/* ===================== TWELVE WEEKS, AND THE MIX ===================== */

/*
 * A column per week: the hours done drawn solid from the bottom, and a line
 * across where the plan asked you to reach. Short of the line is a week you
 * gave something up; past it, one you gave something extra — both readable
 * without reading a number.
 */
.wk-chart {
    display: flex;
    align-items: flex-end;
    gap: 3px;
    margin: 0.9rem 0 0.2rem;
}

.wk {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.3rem;
}

.wk-col {
    position: relative;
    width: 100%;
    height: 5.2rem;
    border-radius: 3px;
    background: var(--color-surface-2);
    overflow: visible;
}

.wk-fill {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    border-radius: 3px;
    background: var(--accent);
    opacity: 0.85;
}

.wk.is-now .wk-fill { opacity: 1; }

/* The line the plan asked for. It sits above the fill when the week fell
   short, and inside it when the week went past. */
.wk-target {
    position: absolute;
    left: -1px;
    right: -1px;
    height: 2px;
    margin-bottom: -1px;
    border-radius: 1px;
    background: var(--color-text);
    opacity: 0.55;
}

.wk-label {
    font-size: 0.58rem;
    line-height: 1;
    white-space: nowrap;
    color: var(--color-text-secondary);
}

.wk.is-now .wk-label { color: var(--color-text); font-weight: 700; }

/* ---- where the hours went ---- */

.mix-bar {
    display: flex;
    height: 0.85rem;
    margin: 0.9rem 0 0.7rem;
    border-radius: 999px;
    overflow: hidden;
    background: var(--color-surface-2);
}

.mix-part { background: var(--sport, var(--accent)); }

.mix-rows { display: flex; flex-direction: column; gap: 0.4rem; }

.mix-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.8rem;
}

.mix-dot {
    flex: 0 0 auto;
    width: 0.6rem;
    height: 0.6rem;
    border-radius: 50%;
    background: var(--sport, var(--accent));
}

.mix-label { flex: 1 1 auto; min-width: 0; color: var(--color-text); font-weight: 600; }
.mix-hours { flex: 0 0 auto; color: var(--color-text); font-variant-numeric: tabular-nums; }

.mix-share {
    flex: 0 0 2.4rem;
    text-align: right;
    font-weight: 700;
    color: var(--color-text);
    font-variant-numeric: tabular-nums;
}

.mix-plan {
    flex: 0 0 5.2rem;
    text-align: right;
    font-size: 0.72rem;
    color: var(--color-text-secondary);
    white-space: nowrap;
}

.mix-drift { color: var(--color-warning); font-weight: 700; }

/*
 * Logging that has not reached the workbook for a day or more. Amber rather than red: it
 * is not broken yet, and shouting about it every time he opens the app would teach him to
 * ignore it. It only ever appears when something really is stuck.
 */
.waiting-warn {
    border-left: 4px solid var(--warn, #c98a00);
    background: color-mix(in srgb, var(--warn, #c98a00) 8%, var(--card));
}
.waiting-warn-title {
    margin: 0 0 4px;
    font-weight: 700;
    font-size: 1.05rem;
}
.waiting-warn-body {
    margin: 0 0 12px;
    color: var(--muted);
    line-height: 1.45;
}
