/* ============================================================================
   RockSolid — base layer.
   Every value here comes from tokens.css. A raw colour in this file is a bug.
   The real screens arrive in Milestone 2; this covers the foundation pages.
   ============================================================================ */

* { box-sizing: border-box; }

/* The browser hides a [hidden] element with `display: none` at user-agent
   weight, which any class setting `display` silently outranks. That is not
   theoretical: giving .button a display (so an anchor could be 48px tall)
   un-hid the routine editor's add-item button and the rest timer's reset —
   both of which are hidden until their script decides otherwise. !important
   here rather than at every call site, because the next `display` written six
   months from now will not remember this either. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font-family: var(--font-ui);
    font-size: var(--step-0);
    line-height: var(--leading-body);
    -webkit-font-smoothing: antialiased;
}

.shell {
    max-width: 46rem;
    margin: 0 auto;
    padding: var(--space-6) var(--space-4) var(--space-7);
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
}

/* Under a top bar the page starts straight below it. The date is the screen's own
   heading and the bar is already a hard edge above it, so 32px of air between the
   two only pushes the sets further down the screen.
   Signed out there is no bar, and that padding is the only thing between the brand
   plate and the top of the window — hence the sibling selector rather than a
   smaller value here. (A general sibling, because the menu script sits between
   the two in the markup.) */
.topbar ~ .shell { padding-top: var(--space-2); }

h1, h2, h3 {
    margin: 0;
    letter-spacing: var(--tracking-tight);
    line-height: var(--leading-tight);
    text-wrap: balance;
}

h1 { font-size: var(--step-3); font-weight: 800; }
h2 { font-size: var(--step-2); font-weight: 700; }

p { margin: 0; max-width: 60ch; }

a {
    color: var(--accent);
    text-underline-offset: 3px;
}

a:hover { text-decoration: none; }

:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 3px;
    border-radius: var(--radius-sm);
}

code, pre {
    font-family: var(--font-num);
    font-size: var(--step--1);
}

.masthead {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.eyebrow {
    font-family: var(--font-num);
    font-size: var(--step--2);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--text-faint);
}

.lead { color: var(--text-dim); }

.note {
    color: var(--text-dim);
    font-size: var(--step--1);
}

/* facts ------------------------------------------------------------------- */

.facts {
    margin: 0;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
    gap: var(--space-3);
}

.facts > div {
    background: var(--surface);
    border: var(--border-hair) solid var(--line);
    border-radius: var(--radius-md);
    padding: var(--space-3);
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

.facts dt {
    font-family: var(--font-num);
    font-size: var(--step--2);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--text-faint);
}

.facts dd {
    margin: 0;
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--step-1);
}

/* status + checks --------------------------------------------------------- */

.status {
    align-self: flex-start;
    font-family: var(--font-num);
    font-size: var(--step--2);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-pill);
    border: var(--border-hair) solid currentColor;
}

.status--ok   { color: var(--success); }
.status--warn { color: var(--warning); }
.status--fail { color: var(--danger); }

.checks {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

.check {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-2);
    padding: var(--space-3);
    background: var(--surface);
    border: var(--border-hair) solid var(--line);
    border-left-width: 3px;
    border-radius: var(--radius-md);
}

.check--ok   { border-left-color: var(--success); }
.check--warn { border-left-color: var(--warning); }
.check--fail { border-left-color: var(--danger); }

.check__label { font-weight: 650; }

.check__detail {
    margin-left: auto;
    font-family: var(--font-num);
    font-size: var(--step--1);
    color: var(--text-dim);
}

/* controls ---------------------------------------------------------------- */

.button {
    /* An anchor is an inline box, and min-height does not apply to one: every
       `<a class="button">` outside a flex parent was rendering 19px tall — a
       third of the tap target it claimed. Blockified here rather than at each
       call site, which is how .datenav__arrow had already patched around it. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: var(--tap-min);
    padding: 0 var(--space-5);
    border: 0;
    border-radius: var(--radius-md);
    background: var(--accent);
    color: var(--accent-contrast);
    font-family: var(--font-num);
    font-size: var(--step--1);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    font-weight: 700;
    text-decoration: none;   /* an <a class="button"> was drawing the link underline */
    cursor: pointer;
}

.button:hover { filter: brightness(1.05); }

/* A button that will not do anything has to look like it. Dimmed rather than
   hidden: the row is incomplete, not forbidden, and a control that vanishes
   while you are typing into the field beside it is worse than one that waits.
   Contrast is deliberately low here — WCAG exempts disabled controls, and the
   point is precisely that it does not invite the thumb. */
.button[disabled] {
    opacity: 0.45;
    cursor: default;
}

.button[disabled]:hover { filter: none; }

.button--quiet {
    background: transparent;
    color: var(--text);
    border: var(--border-hair) solid var(--line);
    padding: 0 var(--space-3);
}

/* A standalone action with nothing around it to say it is one, so the outline is
   the only thing that makes it read as a control — and --line is 1.27:1 against
   the page, which is no border at all. This one draws in the dim text colour:
   5.4:1 on paper, 6.1:1 on plate. --quiet stays hairline, because it is only
   ever used inside a card or a row where the context already does that work. */
.button--outline {
    background: transparent;
    color: var(--text);
    border: var(--border-hair) solid var(--text-dim);
    padding: 0 var(--space-4);
}

/* Cancel is the other half of one decision, so it is the same button as save:
   same height, same shape, same uppercase label. Only the colour differs — a
   discard wearing the save colour is a mis-tap waiting to happen with a chalky
   thumb. The sunken fill is 1.11:1 against the page and could not stand alone,
   so the dim border does the work of saying "control". */
.button--cancel {
    background: var(--surface-sunken);
    color: var(--text);
    border: var(--border-hair) solid var(--text-dim);
}

/* The walk timer. Nothing counts while it runs — see the comment on the script
   in cardio/index.php — so this is a state line and two buttons, not a clock. */
/* Idle, this is just a button — no frame, because there is nothing to frame.
   The panel exists to hold "on a walk since 19:01" beside its two controls, and
   an empty box with a button pushed into one corner is what you get when a
   container outlives its contents. */
.walkclock {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
}

/* A walk is running: now there is state to show, so the box earns its border. */
.walkclock--live {
    padding: var(--space-3);
    background: var(--surface-sunken);
    border: var(--border-hair) solid var(--line);
    border-radius: var(--radius-md);
}

.walkclock__state {
    font-family: var(--font-num);
    font-size: var(--step--1);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--text-dim);
}

.walkclock__state:empty { display: none; }

.walkclock__acts {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

/* Only pushed to the right when there is text on the left to push away from. */
.walkclock--live .walkclock__acts { margin-left: auto; }

/* Two fields that belong together — a date and the time of day — side by side,
   and stacked once there is not room for both without squeezing the inputs. */
.fieldrow {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
}

.fieldrow > .field { flex: 1 1 10rem; }

/* Save and cancel on one row. Wraps on a narrow phone rather than shrinking
   either one below its tap height. */
.formactions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    align-items: center;
}

.button--danger { background: var(--danger); color: var(--accent-contrast); }

.debug {
    background: var(--surface-sunken);
    border: var(--border-hair) solid var(--line);
    border-radius: var(--radius-md);
    padding: var(--space-3);
    overflow-x: auto;
    color: var(--text-dim);
}

/* logo -------------------------------------------------------------------- */

/* The lockup: mark above (or beside) a heavy wide-tracked wordmark, with the
   tagline in small caps under it. The wordmark is live text rather than an
   image, so it stays crisp at any size, translates, and can be read aloud. */

.logo {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--text);
    text-decoration: none;
}

/* mark.svg is a mask, not an image: the gradient is painted through its shape,
   which is what lets one file serve both grounds. mask-mode is stated explicitly
   because the default, match-source, is read differently across engines — and
   under a luminance reading the mark disappears entirely. */
.logo__mark {
    flex: none;
    display: block;
    background: linear-gradient(180deg, var(--logo-hi), var(--logo-lo));
    -webkit-mask-image: url("/assets/img/logo-mark.svg");
    mask-image: url("/assets/img/logo-mark.svg");
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-position: center;
    mask-position: center;
    -webkit-mask-size: contain;
    mask-size: contain;
    mask-mode: alpha;
}

.logo__word {
    font-weight: 800;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    line-height: 1;
    /* The type is the steel too, so the lockup reads as one object. */
    background: linear-gradient(180deg, var(--logo-hi), var(--logo-lo));
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

.logo__sub {
    font-family: var(--font-num);
    font-size: var(--step--2);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--text-faint);
}

.logo--small .logo__mark { width: 28px; height: 28px; }
.logo--small .logo__word { font-size: var(--step-0); }

/* The supplied artwork. Transparent vector, so on Plate Black it sits straight on
   the ground it was drawn for and --logo-plate is `transparent`; on paper the
   plate comes back, because polished silver on white is invisible.

   Only used large. At top-bar size 167 paths of musculature is mush however
   sharp the vector — that is what the simplified mark above is for. */
.brandplate {
    align-self: center;
    background: var(--logo-plate);
    border-radius: var(--radius-lg);
    padding: var(--space-4) var(--space-5);
    max-width: 22rem;
    width: 100%;
}

.brandplate__img {
    display: block;
    width: 100%;
    height: auto;
}

.brandplate__tagline {
    align-self: center;
    font-family: var(--font-num);
    font-size: var(--step--2);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--text-faint);
}

/* top bar ----------------------------------------------------------------- */

.topbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    background: var(--surface);
    border-bottom: var(--border-hair) solid var(--line);
}

/* The identity and the screen name as one block, pushing everything else right. */
.topbar__id {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    margin-right: auto;
}

.topbar__brand {
    font-weight: 800;
    letter-spacing: var(--tracking-tight);
    color: var(--text);
    text-decoration: none;
}

/* Indented to start under the wordmark, not under the mark: the label belongs to
   the words. 28px is the small mark (see .logo--small), plus the lockup's gap. */
.topbar__screen {
    margin: 0;
    padding-left: calc(28px + var(--space-2));
    font-family: var(--font-num);
    font-size: var(--step--2);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--text-faint);
}

.topbar__nav {
    margin-left: auto;
    display: flex;
    align-items: center;
    /* Wraps rather than clips: on a phone with no JavaScript this is the whole
       fallback, and a nav that runs off the edge of the screen loses the sign-out
       button entirely — which is exactly what it was doing. */
    flex-wrap: wrap;
    gap: var(--space-3);
}

.topbar__who {
    font-family: var(--font-num);
    font-size: var(--step--1);
    color: var(--text-dim);
}

/* bottom tab bar ----------------------------------------------------------- */

/* Phone only, and only the screens you move between mid-session: the top of a
   phone is the hardest place to reach with the hand that is not holding a bar. */
.tabbar { display: none; }

@media (max-width: 44rem) {
    .tabbar {
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 10;
        display: flex;
        background: var(--surface);
        border-top: var(--border-hair) solid var(--line);
        /* Clears the home indicator on a phone without a button. */
        padding-bottom: env(safe-area-inset-bottom, 0);
    }

    .tabbar__tab {
        flex: 1;
        min-height: var(--tap-min);
        display: flex;
        align-items: center;
        justify-content: center;
        color: var(--text-dim);
        text-decoration: none;
        /* The active marker is a line along the top edge. Transparent when off, so
           the icons never shift by a pixel as you move between tabs. */
        border-top: 2px solid transparent;
        margin-top: -1px;
    }

    /* Colour *and* the line: which tab you are on must not depend on hue alone. */
    .tabbar__tab--on {
        color: var(--accent);
        border-top-color: var(--accent);
        background: var(--accent-tint);
    }

    /* The bar sits over the page, so the page needs somewhere to end. */
    .shell { padding-bottom: calc(var(--tap-min) + var(--space-6)); }
}

/* hamburger ---------------------------------------------------------------- */

/* Hidden unless the script has marked the bar collapsible AND the screen is
   narrow. A button that does nothing is worse than no button. */
.burger {
    display: none;
    margin-left: auto;
    width: var(--tap-min);
    height: var(--tap-min);
    align-items: center;
    justify-content: center;
    background: transparent;
    border: var(--border-hair) solid var(--line);
    border-radius: var(--radius-md);
    cursor: pointer;
}

/* Three bars from one element: the middle is the box, the other two are its
   before and after. Nothing to load, nothing to go missing. */
.burger__bars,
.burger__bars::before,
.burger__bars::after {
    display: block;
    width: 18px;
    height: 2px;
    background: var(--text);
    content: "";
    position: relative;
}

.burger__bars::before { top: -6px; }
.burger__bars::after  { top: 4px; }

@media (max-width: 44rem) {
    .topbar[data-menu] { flex-wrap: wrap; }

    /* Every menu row is a thumb's height, with or without the script. The open-menu
       rule below sets this too, but only once JavaScript has marked the bar
       collapsible — and the no-JS fallback, where the nav sits inline and wraps, is a
       real state that was shipping 25px links. tokens.css rule 4 has no exception for
       "only when the script did not load". */
    .topbar__nav > * { min-height: var(--tap-min); display: flex; align-items: center; }

    .topbar[data-menu] .burger { display: inline-flex; }

    .topbar[data-menu="closed"] .topbar__nav { display: none; }

    /* Open, it is a column across the full width, pushing the page down rather
       than floating over it — no overlay, no scroll locking, nothing to trap a
       thumb behind. */
    .topbar[data-menu="open"] .topbar__nav {
        width: 100%;
        margin: var(--space-2) 0 0;
        flex-direction: column;
        align-items: stretch;
        gap: 0;
    }

    .topbar[data-menu="open"] .topbar__nav > * {
        min-height: var(--tap-min);
        display: flex;
        align-items: center;
        border-top: var(--border-hair) solid var(--line);
    }

    .topbar[data-menu="open"] .topbar__nav form { padding: var(--space-2) 0; }
}

/* forms ------------------------------------------------------------------- */

.form {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    align-items: flex-start;
    max-width: 26rem;
    width: 100%;
}

.field {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    width: 100%;
}

.field label {
    font-family: var(--font-num);
    font-size: var(--step--2);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--text-faint);
}

/* A label with something to say beside it. Baseline-aligned rather than
   centred, so the two sit on one line however the type scale moves. */
/* A checkbox and its words as one target. .field label is the eyebrow style —
   tiny, faint, uppercase — which is right for a caption sitting above an
   input and wrong beside a box you tick, because this one is a sentence and
   has to read like one. Specificity is why it is written .field .switch: a
   bare .switch loses to .field label, which is a class and an element.

   48px of row, so the words are as tappable as the 20px box beside them. */
.field .switch {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-height: var(--tap-min);
    font-family: var(--font-ui);
    font-size: var(--step-0);
    letter-spacing: normal;
    text-transform: none;
    color: var(--text);
    cursor: pointer;
}

/* A hint that is not neutral: the setting above it is on and doing nothing.
   Warning rather than danger — nothing is broken, this device simply cannot. */
.field__hint.field__hint--warn { color: var(--warning); }

.field__head {
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
}

/* Not a flash and not a status pill: it is on for as long as the box and the
   stored note agree, and it goes the moment they stop agreeing. That is the
   only claim worth making about a text box somebody is typing into — the
   button says "just saved", this says "still saved". Same words as the pill,
   in the success colour, which is already carried as text on the health page. */
.field__saved {
    font-family: var(--font-num);
    font-size: var(--step--2);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--success);
}

.field__hint {
    font-size: var(--step--1);
    color: var(--text-dim);
}

/* Every field a person types into, listed by type because that is the only way
   to catch text-like inputs without also catching checkboxes and submit buttons.
   **Add new types here.** A type missing from this list is not "slightly off" —
   it gets the browser's own widget, and on iOS that meant the rest-timer number
   field rendering as a black box on a light screen, while the date and time on
   the walk form and every textarea in the app went their own separate ways.

   Kept as an explicit list rather than input:not([type=checkbox])… on purpose:
   the negation form outranks `.setrow__edit input[type="text"]` and the other
   places that deliberately override these, and would break all of them. */
input[type="date"],
input[type="datetime-local"],
input[type="email"],
input[type="number"],




input[type="password"],
input[type="search"],
input[type="tel"],
input[type="text"],
input[type="time"],
input[type="url"],
select,
textarea {
    /* 16px minimum: anything smaller makes iOS zoom on focus. */
    font-size: var(--step-0);
    font-family: inherit;
    /* A fixed height, not a minimum. Safari adds its own internal padding to
       some field types, so a shared minimum still left the number box standing
       taller than the selects above it on a phone while measuring 48px in a
       desktop browser. Pinning it settles every single-line field at the same
       height everywhere; the textarea opts out below, because it holds lines. */
    height: var(--tap-min);
    padding: 0 var(--space-3);
    color: var(--text);
    background: var(--surface);
    border: var(--border-hair) solid var(--line);
    border-radius: var(--radius-md);
    width: 100%;
}

/* Checkboxes keep the browser's box and gain the app's colour. accent-color is
   one line and does what a hand-drawn checkbox needs thirty to do badly: the
   tick, the focus ring, the disabled state and the contrast of the mark against
   the fill all stay the browser's job, in both themes, at any zoom. Without it
   the warm-up flag was the one control on the screen still wearing iOS blue in
   an app whose accent is rust on paper and lime on plate. */
input[type="checkbox"],
input[type="radio"] {
    accent-color: var(--accent);
    width: 1.25rem;
    height: 1.25rem;
}

/* A textarea holds lines rather than one line, so it needs the padding a single
   centred row does not, and room to breathe between them. */
textarea {
    /* Exactly two fields tall, so its height is obviously a decision rather
       than whatever `rows="2"` happened to compute — which was 68px, taller
       than a field by an amount that read as an accident. */
    height: auto;
    min-height: calc(var(--tap-min) * 2);
    padding: var(--space-2) var(--space-3);
    line-height: var(--leading-body);
    resize: vertical;
}

input:focus-visible, select:focus-visible { border-color: var(--accent-edge); }

/* messages ---------------------------------------------------------------- */

.alert, .flash {
    padding: var(--space-3);
    border-radius: var(--radius-md);
    border: var(--border-hair) solid var(--line);
    border-left-width: 3px;
    background: var(--surface);
    font-size: var(--step--1);
    max-width: none;
}

.alert--error { border-left-color: var(--danger); }
.alert--warn  { border-left-color: var(--warning); }
.alert--ok    { border-left-color: var(--success); }
.flash        { border-left-color: var(--accent); }

.link-box {
    background: var(--surface-sunken);
    border: var(--border-hair) solid var(--line);
    border-radius: var(--radius-md);
    padding: var(--space-3);
    overflow-wrap: anywhere;
    max-width: none;
}

/* tables ------------------------------------------------------------------ */

.table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--step--1);
    display: block;
    overflow-x: auto;
}

.table th {
    text-align: left;
    font-family: var(--font-num);
    font-size: var(--step--2);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--text-faint);
    font-weight: 500;
    padding: var(--space-2);
    border-bottom: var(--border-hair) solid var(--line);
}

.table td {
    padding: var(--space-3) var(--space-2);
    border-bottom: var(--border-hair) solid var(--line);
    vertical-align: top;
}

.num { font-family: var(--font-num); font-variant-numeric: tabular-nums; }

.muted { color: var(--text-dim); }

.actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

.actions form { display: flex; gap: var(--space-1); }

.actions .danger input { min-height: 0; width: 9rem; }

.pill {
    font-family: var(--font-num);
    font-size: var(--step--2);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-pill);
    border: var(--border-hair) solid currentColor;
}

.pill--active    { color: var(--success); }
.pill--pending   { color: var(--warning); }
.pill--suspended { color: var(--danger); }

/* search, filters, cards -------------------------------------------------- */

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* Three controls now — search, group, submit — so the row is allowed to break.
   On a phone the box and the button take the first line and the group select
   drops to its own; on anything wider all three sit together. Basis rather
   than a breakpoint, so it reflows on content instead of on a guess about
   which devices exist. */
.searchbar {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    align-items: stretch;
}

.searchbar input {
    /* Looks come from the shared field rule; this only has to share the row. */
    flex: 1 1 11rem;
}

.searchbar select {
    flex: 1 1 11rem;
}

.chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

.chip {
    /* Full tap height even though it reads as a small label: these are the
       controls a chalky thumb hits while standing at the rack. */
    display: inline-flex;
    align-items: center;
    min-height: var(--tap-min);
    padding: 0 var(--space-3);
    border: var(--border-hair) solid var(--line);
    border-radius: var(--radius-pill);
    background: var(--surface);
    color: var(--text-dim);
    font-size: var(--step--1);
    text-decoration: none;
}

.chip--on {
    color: var(--text);
    border-color: var(--accent-edge);
    background: var(--accent-tint);
}

.cards {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.card {
    background: var(--surface);
    border: var(--border-hair) solid var(--line);
    border-radius: var(--radius-md);
    padding: var(--space-3);
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

.card__title {
    font-size: var(--step-1);
    font-weight: 650;
    color: var(--text);
    text-decoration: none;
}

.card__meta {
    font-size: var(--step--1);
    color: var(--text-dim);
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    align-items: center;
}

/* The library editor puts a whole row's fields on one line; it is an admin
   tool on a desktop, so it scrolls sideways rather than reflowing. */
.inline-form {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    align-items: center;
}

.inline-form input[type="text"] { width: 11rem; }
.inline-form select { width: auto; }

/* log --------------------------------------------------------------------- */

/* The day: back, the date, forward and today — one row, read left to right.
   Both outer columns are the width of two buttons whether or not two buttons are
   in them, so the date sits on the centre of the screen and stays there: today
   appears the moment you leave today, and a date that jumped sideways as you
   paged through the week would be the most obvious thing on the screen. */
.datenav {
    /* Narrower than a tap target is tall, and only in this row: two of them plus a
       long Icelandic month have to share 375 pixels, and at the full 48 the date
       wraps onto a second line on some days and not others — a heading that
       changes height as you page through the week. The height stays --tap-min, so
       these are 44 × 48: still a thumb's worth of button. */
    --datenav-btn: calc(var(--tap-min) - var(--space-1));
    --datenav-slot: calc(var(--datenav-btn) * 2);

    display: grid;
    grid-template-columns: var(--datenav-slot) minmax(0, 1fr) var(--datenav-slot);
    align-items: center;
    gap: var(--space-1);
}

.datenav__side { display: flex; }
.datenav__side--end { justify-content: flex-end; }

.datenav__arrow {
    flex: none;
    width: var(--datenav-btn);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    font-size: var(--step-1);
}

.datenav__date {
    min-width: 0;
    text-align: center;
    /* The heading is the biggest thing on the screen and Icelandic month names are
       long; let it shrink between the arrows before it wraps under them. The floor
       is for the rare heading that carries a year — see Formatter::dateHeading(). */
    font-size: clamp(var(--step-1), 5.6vw, var(--step-3));
    text-wrap: balance;
}


.entry {
    background: var(--surface);
    border: var(--border-hair) solid var(--line);
    border-radius: var(--radius-md);
    padding: var(--space-3);
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.entry__head {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    align-items: baseline;
    justify-content: space-between;
}

/* What you did last time, stated quietly — it is a reference, not the data. */
.ghost {
    font-family: var(--font-num);
    font-size: var(--step--1);
    color: var(--text-faint);
}

/* What is known about an exercise before you lift it: last set, the routine's
   target, your best. One wrapping line rather than three stacked ones, because
   a session has eight of these and this screen is already long.

   The label is faint and the value is not: you scan for the number, and the
   word only has to say which number it is. */
.cue {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-1) var(--space-4);
    font-family: var(--font-num);
    font-size: var(--step--1);
}

.cue__part {
    display: inline-flex;
    align-items: baseline;
    gap: var(--space-2);
}

.cue__label {
    font-size: var(--step--2);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--text-faint);
}

.cue__value {
    color: var(--text);
    font-variant-numeric: tabular-nums;
}

/* The one opinion on the row, so it is the one thing here that is coloured.
   Success rather than accent: it is a thing that went well, not a control. */
.cue__nudge {
    font-size: var(--step--2);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--success);
}

.sets {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

/* One line per set, and the same line whether or not it is being edited. Six
   controls have to share about 346px on a phone, so the gap is --space-1: bordered
   fields sitting 4px apart still read as separate, and 8px did not fit. */
.setrow {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    align-items: center;
    padding-bottom: var(--space-2);
    border-bottom: var(--border-hair) solid var(--line);
}

/* A warm-up is real but does not count; it recedes rather than shouting. */
.setrow--warmup { opacity: 0.62; }

.setrow__edit {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    align-items: center;
}

.setrow__number {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    color: var(--text-faint);
    min-width: 1.25rem;
}

/* Numbers are typed one-handed mid-set: wide enough to hit, narrow enough that a
   whole set fits on one line of a phone — 3.75rem holds "100,5" with the padding
   pulled in from --space-3, and the numerals are centred because a right-aligned
   number in a 60px box reads as pushed against the wall.
   The selector needs the attribute in it to outrank `input[type="text"]` above —
   as a bare class it lost, and every set row rendered as a full-width column of
   giant fields. */
.setrow__edit input[type="text"] {
    width: 3.75rem;
    padding: 0 var(--space-1);
    text-align: center;
}

/* Save and delete: square, icon-sized, and the same size as each other so the end
   of every row lines up whether or not it has been edited. Both are 44 wide by the
   button's --tap-min tall — the delete was 35 wide, under any thumb's minimum. */
.setrow__act {
    flex: none;
    width: 44px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: var(--step-1);
}

/* The last slot in a row is "what to do with this row", and it holds one control
   at a time: delete, until the row has unsaved changes, and then save. A permanent
   save button was the widest thing in the row and pushed it onto three lines; both
   controls at once do not fit either — the row is 317px on a phone once the card's
   padding is out, and six controls plus a second action come to 327.

   So they share the slot. Nothing moves as you type, which on this screen is the
   difference between editing a number and losing your place in the session.

   [data-live] is set by app.js, so this only ever hides anything on a page whose
   script is running. With no script both are always visible and the row wraps —
   uglier, but the plain form posts are then the only way to save or delete. */
[data-live] .setrow:not([data-dirty]) .setrow__save { display: none; }
[data-live] .setrow[data-dirty] form[data-op="set.delete"] { display: none; }

/* The warm-up flag on a set. It used to borrow .check from the health page, which
   is a bordered card — hence the boxed look and 12px of padding inside every set
   row. Its own component now; .check went back to being one thing. */
.setflag {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    min-height: var(--tap-min);
    font-size: var(--step--1);
    color: var(--text-dim);
}

/* .setflag input needs no size of its own: the checkbox rule above sets it. */

/* Below 30rem the word steps aside so the row stays on one line; the checkbox
   keeps the name as its aria-label, and the label keeps it as a title. The add-set
   form directly beneath always shows it in full, which is where a warm-up is
   marked in the first place. */
@media (max-width: 30rem) {
    /* With the word hidden the label shrinks to the checkbox — a 27px-wide target for
       a toggle. The row has room: measured 26px of slack at 375px, and this takes 17. */
    .setflag {
        min-width: 44px;
        justify-content: center;
    }

    .setflag__word {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
}

.addset {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    align-items: center;
}

.addset input[type="text"] { width: 5rem; }


/* A folded note. The marker is the browsers own triangle, which already says
   open and closed in every locale and rotates without help. What it needs from
   here is a tap target: a bare <summary> is one line of text tall, well under
   the 48px this app holds itself to everywhere else. */
.howto__summary {
    display: flex;
    align-items: center;
    min-height: var(--tap-min);
    gap: var(--space-2);
    font-family: var(--font-num);
    font-size: var(--step--2);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--text-dim);
    cursor: pointer;
}

.howto[open] .howto__summary { margin-bottom: var(--space-2); }

/* The guides inside it. Numbered, because the order is the order of the
   session and the third exercise being third is information. The name is
   full-strength text and the instruction is dim: it is the name you scan
   for and the sentence you read once. */
.howto__list {
    margin: 0;
    padding-left: var(--space-5);
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    color: var(--text-dim);
    font-size: var(--step--1);
}

.howto__name {
    display: block;
    color: var(--text);
    font-weight: 650;
}

.howto__text {
    display: block;
    max-width: 60ch;
}

/* Line breaks the author typed are kept. Every note this class renders is
   written by a person in a textarea — a session note, a walk note, the
   instructions on a routine — and a <p> would otherwise collapse the whole
   thing into one block. pre-line and not pre: newlines survive, accidental
   runs of spaces and the template indentation do not. */
.sessionnote {
    white-space: pre-line;
    font-family: var(--font-note);
    font-size: var(--step-0);
    background: var(--surface);
    border-left: 3px solid var(--line);
    padding: var(--space-3);
}

.timer {
    display: flex;
    gap: var(--space-2);
    align-items: center;
    background: var(--surface-sunken);
    border: var(--border-hair) solid var(--line);
    border-radius: var(--radius-md);
    padding: var(--space-1) var(--space-2);
}

/* charts ------------------------------------------------------------------ */

/* Server-rendered inline SVG (Core/Chart.php). Colour lives here rather than in
   the markup, so both themes swap underneath a chart that knows nothing about
   either — and so a chart prints as ink on paper rather than as a dark block. */
.chart {
    display: block;
    width: 100%;
    height: auto;
    max-height: 15rem;
    overflow: visible;
}

.chart__axis {
    stroke: var(--line);
    stroke-width: 1;
}

.chart__bar { fill: var(--accent); }

.chart__line {
    stroke: var(--accent);
    stroke-width: 2.5;
    stroke-linejoin: round;
    stroke-linecap: round;
}

.chart__dot {
    fill: var(--accent);
    stroke: var(--surface);
    stroke-width: 1.5;
}

.chart__label {
    fill: var(--text-faint);
    font-family: var(--font-num);
    font-size: 13px;      /* SVG user units, not the page's type scale */
    letter-spacing: 0.04em;
}

.panel {
    background: var(--surface);
    border: var(--border-hair) solid var(--line);
    border-radius: var(--radius-md);
    padding: var(--space-3);
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.panel__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-2);
    flex-wrap: wrap;
}

.panel__title {
    font-size: var(--step-1);
    font-weight: 650;
}

.panel__meta {
    font-family: var(--font-num);
    font-size: var(--step--2);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--text-faint);
}

/* The chart's own numbers, repeated as text. Not a fallback — the table is how
   the data is read aloud, and how it is read at all when the trend is two points
   and the picture says less than the pair of numbers does. */
.chartdata {
    width: 100%;
    border-collapse: collapse;
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: var(--step--1);
}

.chartdata th,
.chartdata td {
    text-align: left;
    padding: var(--space-1) var(--space-2);
    border-bottom: var(--border-hair) solid var(--line);
}

.chartdata td:last-child,
.chartdata th:last-child { text-align: right; }

/* A horizontal bar per muscle group: the bar is a background on the row itself, so
   there is no second element to keep in sync with the number.
   The share arrives as a data attribute in deciles rather than as an inline style,
   because this app's CSP carries no unsafe-inline and a nonce does not apply to
   style *attributes* — an inline width is dropped and the bar silently vanishes.
   Twenty-one rules is the price of that — five-point steps, because at ten 138 sets
   and 142 sets drew the same full bar. tools/lint.php now refuses the shortcut. */
.grouprow[data-share="0"] { --share: 0%; }
.grouprow[data-share="5"] { --share: 5%; }
.grouprow[data-share="10"] { --share: 10%; }
.grouprow[data-share="15"] { --share: 15%; }
.grouprow[data-share="20"] { --share: 20%; }
.grouprow[data-share="25"] { --share: 25%; }
.grouprow[data-share="30"] { --share: 30%; }
.grouprow[data-share="35"] { --share: 35%; }
.grouprow[data-share="40"] { --share: 40%; }
.grouprow[data-share="45"] { --share: 45%; }
.grouprow[data-share="50"] { --share: 50%; }
.grouprow[data-share="55"] { --share: 55%; }
.grouprow[data-share="60"] { --share: 60%; }
.grouprow[data-share="65"] { --share: 65%; }
.grouprow[data-share="70"] { --share: 70%; }
.grouprow[data-share="75"] { --share: 75%; }
.grouprow[data-share="80"] { --share: 80%; }
.grouprow[data-share="85"] { --share: 85%; }
.grouprow[data-share="90"] { --share: 90%; }
.grouprow[data-share="95"] { --share: 95%; }
.grouprow[data-share="100"] { --share: 100%; }

.grouprow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    background:
        linear-gradient(to right, var(--accent-tint) var(--share, 0%), transparent var(--share, 0%));
    border: var(--border-hair) solid var(--line);
}

.grouprow__name { font-weight: 650; }

.grouprow__count {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    color: var(--text-dim);
}

/* Personal records. The kind is a label, the value is the number, the date is when
   — in that order, because "what" before "how much" before "when" is how you read
   a record out loud. */
.prlist {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

/* One exercise's four records, folded. The name and the heaviest sit on the
   closed row because that pair is what somebody means by "my record"; the
   other three are a tap away and were, until now, three more rows about the
   same set on the same day.

   48px of summary, the same tap minimum as everything else. */
.prgroup__summary {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-2);
    min-height: var(--tap-min);
    padding-bottom: var(--space-2);
    border-bottom: var(--border-hair) solid var(--line);
    cursor: pointer;
}

.prgroup__exercise { font-weight: 650; }

/* Pushed right, so the numbers line up down the closed list and you can read
   the column without opening anything. */
.prgroup__headline {
    margin-left: auto;
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    color: var(--accent);
}

/* Indented under the summary, and the last row loses its rule so the group
   does not end on a line that belongs to the one below it. */
.prlist--inner {
    padding: var(--space-2) 0 var(--space-2) var(--space-4);
}

.prlist--inner .prrow:last-child { border-bottom: 0; padding-bottom: 0; }

.prrow {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-2);
    padding-bottom: var(--space-2);
    border-bottom: var(--border-hair) solid var(--line);
}

.prrow__exercise { font-weight: 650; }

.prrow__kind {
    font-family: var(--font-num);
    font-size: var(--step--2);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--text-faint);
}

.prrow__value {
    margin-left: auto;
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    color: var(--pr);
}

.prrow__date {
    font-family: var(--font-num);
    font-size: var(--step--1);
    color: var(--text-dim);
    width: 100%;
}

/* routines ---------------------------------------------------------------- */

.card__head {
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
    flex-wrap: wrap;
}

/* An archived routine is still readable — it explains a year of sessions — but it
   is not what you are looking for, so it recedes. Never by colour alone: the card
   also says "Í geymslu" in words. */
.card--archived { opacity: 0.66; }

.card__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    align-items: center;
    margin-top: var(--space-2);
}

/* The plan itself. Numbered, because the order is the routine. */
.routine__list {
    margin: 0;
    padding-left: var(--space-5);
    display: flex;
    flex-direction: column;
    gap: 2px;
    font-size: var(--step--1);
    color: var(--text-dim);
}

.routine__name { color: var(--text); }

.routine__target {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    color: var(--text-dim);
}

.routine__target::before { content: " — "; }

/* The editor. One block per exercise rather than a five-column grid, because a
   grid that wide cannot reflow onto the phone this gets written on. */
.ritems {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.ritem {
    background: var(--surface);
    border: var(--border-hair) solid var(--line);
    border-radius: var(--radius-md);
    padding: var(--space-3);
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

/* The targets wrap as a row of small labelled boxes: five of them fit a desktop
   line and two or three fit a phone's, with the label above each field so a
   number is never ambiguous once you come back to it a month later. */
.ritem__targets {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

.ritem__field {
    display: flex;
    flex-direction: column;
    gap: 2px;
    font-family: var(--font-num);
    font-size: var(--step--2);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--text-faint);
}

.ritem__field input[type="text"] {
    width: 4.5rem;
    text-align: center;
    padding: 0 var(--space-1);
}

/* Start from a routine, on an empty day. Each routine is one button with its name
   and its size on it, so starting a session is a single tap on the thing you
   recognise rather than a menu to read. */
.fromroutine {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.fromroutine__list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

.fromroutine__pick {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    padding: var(--space-2) var(--space-4);
    text-transform: none;
    letter-spacing: normal;
}

.fromroutine__name {
    font-size: var(--step-0);
    font-weight: 650;
}

.fromroutine__meta {
    font-family: var(--font-num);
    font-size: var(--step--2);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--text-faint);
}

/* ---- the timer floats ----------------------------------------------------
   A rest is timed while you are looking at the sets below it, so the control is
   never in the part of the page you happen to have scrolled to. It floats.

   On a phone it is fixed above the tab bar, wherever you are — the bottom third is
   where a thumb reaches mid-set without regripping (tokens.css rule 4). On a
   desktop it starts under the date and sticks to the top edge once the page scrolls
   past it: there are no thumbs there, and a control fixed over the top of a wide
   window would sit on the header.

   Both are pure CSS. The element is `hidden` until the script reveals it, so a page
   without JavaScript has no timer and nothing floating.
   -------------------------------------------------------------------------- */

@media (min-width: 44.0625rem) {
    .timer {
        position: sticky;
        top: var(--space-2);
        z-index: 9;
        margin: 0 auto;
        width: fit-content;
    }
}

@media (max-width: 44rem) {
    .timer {
        position: fixed;
        left: 50%;
        transform: translateX(-50%);
        /* Clear of the tab bar, and of the home indicator underneath it. */
        bottom: calc(var(--tap-min) + var(--space-3) + env(safe-area-inset-bottom, 0px));
        z-index: 9;
        background: var(--surface);
        box-shadow: var(--shadow-card);
    }

    /* Room to scroll the last set clear of it. Only on a screen that has a timer:
       the class is added by the log screen's script. */
    .shell--timer { padding-bottom: calc(var(--tap-min) * 2 + var(--space-7)); }
}

/* sync state -------------------------------------------------------------- */

/* Honest, and never reassuring beyond what is true: "on this phone" until the
   server has actually acknowledged it. Warning-coloured while queued, because
   queued is a real state the user may need to act on, not a decoration. */
/* inline-flex, not align-self: this sits inside a plain block, where align-self
   does nothing — which is why it stretched the full width of the screen instead
   of hugging its own text. */
/* In the top bar, beside the menu. Present only when it has something to say, so
   its mere appearance is information — see paint() in app.js. */
.sync {
    flex: none;
    margin: 0;
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    width: fit-content;
    font-family: var(--font-num);
    font-size: var(--step--2);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-pill);
    border: var(--border-hair) solid currentColor;
    color: var(--text-faint);
}

.sync[data-state="queued"]      { color: var(--warning); }
.sync[data-state="waiting"]     { color: var(--warning); }
.sync[data-state="signed-out"]  { color: var(--danger); }
.sync[data-state="synced"]      { color: var(--success); }

/* Every state differs in shape as well as in colour, because on a narrow screen
   the dot is all there is and tokens.css rule 2 holds everywhere: a ring is
   waiting, a filled disc is done, a square is stuck, a diamond needs you. */
.sync__dot {
    flex: none;
    width: 9px;
    height: 9px;
    border: 2px solid currentColor;
    border-radius: var(--radius-pill);
    background: transparent;
}

.sync[data-state="synced"] .sync__dot     { background: currentColor; }
.sync[data-state="waiting"] .sync__dot    { border-radius: 2px; }
.sync[data-state="signed-out"] .sync__dot {
    border-radius: 0;
    background: currentColor;
    transform: rotate(45deg);
}

/* The word steps aside for the logo and the menu at every width where the full
   nav and a 130px pill cannot share one line — measured at about 800px with the
   Icelandic nav, admin link included, so 60rem leaves room for a longer one. Any
   narrower and the top bar wraps to two rows the moment something is queued,
   dropping the whole page 60px as you log a set.

   Visually only: an indicator that says nothing to a screen reader is not an
   indicator, and this one is the difference between a set being saved and a set
   being gone. */
@media (max-width: 60rem) {
    .sync__word {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
}

/* A row that exists on the phone but not yet on the server. Dashed rather than
   faded: faded reads as "less important", and it is not — it is the newest set. */
.setrow[data-pending] {
    border-bottom-style: dashed;
    border-bottom-color: var(--warning);
}

/* calendar ---------------------------------------------------------------- */

.calendar {
    width: 100%;
    border-collapse: collapse;
    table-layout: fixed;
}

.calendar th {
    font-family: var(--font-num);
    font-size: var(--step--2);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--text-faint);
    font-weight: 500;
    padding-bottom: var(--space-2);
}

.cal { padding: 2px; }

.cal__day {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    min-height: var(--tap-min);
    border-radius: var(--radius-sm);
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    color: var(--text);
    text-decoration: none;
}

.cal--outside .cal__day { color: var(--text-faint); }

.cal--today .cal__day {
    border: var(--border-hair) solid var(--line);
    font-weight: 700;
}

.cal__day--trained { background: var(--accent-tint); }

.cal__dot {
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--accent);
}

/* lists ------------------------------------------------------------------- */

.links, .events {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.links { flex-flow: row wrap; gap: var(--space-4); }

.event {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    align-items: baseline;
    font-size: var(--step--1);
    padding-bottom: var(--space-2);
    border-bottom: var(--border-hair) solid var(--line);
}

.event__action { font-family: var(--font-num); }
.event__who    { color: var(--text-dim); }

.event__when {
    margin-left: auto;
    font-family: var(--font-num);
    color: var(--text-faint);
}
