/*
 * Application shell.
 *
 * Logical properties throughout (inline/block rather than left/right), so that
 * Arabic, Farsi and Urdu lay out correctly without a mirrored stylesheet.
 */

/*
 * Widths mean what they say.
 *
 * Without this, `inline-size: 100%` is a hundred per cent of the *content*
 * box and the padding is added on top of it. Measured: a button filling a
 * sheet came out 398 pixels wide inside 358 of usable width, hanging forty
 * over the edge — exactly its own twenty pixels of padding on each side.
 *
 * This is the third time the same arithmetic has bitten this stylesheet. The
 * tooltip was capped at 288 on a 320-pixel phone and measured 317; the number
 * pad's display overflowed its own control; and both were fixed by adding
 * `box-sizing: border-box` to that one rule. Two local patches for a global
 * default, and the third occurrence is the one that says so. Every modern
 * stylesheet begins with this line and this one should have.
 */
*,
*::before,
*::after {
    box-sizing: border-box;
}

/*
 * And no inherited spacing, for the same reason.
 *
 * A browser's default margin on a paragraph or a heading is one em, so it
 * changes with the font size and nobody chose it. Measured across fifteen
 * screens it was producing 10, 14 and 17 pixel gaps beside deliberate ones of
 * 12 and 16 — the same relationship arriving at a different number because one
 * element happened to be a `<p>` and the next a `<div>`.
 *
 * Everything that needs space now says so. Five element types were relying on
 * the default when this was added, which is what made it safe to do.
 */
h1,
h2,
h3,
h4,
p,
ul,
ol,
dl,
dd,
figure {
    margin: 0;
}

body {
    margin: 0;
    min-block-size: 100dvb;
    background: var(--canvas);
    color: var(--text-main);
    font-family: var(--font-sans);
    font-size: 0.875rem;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
}

/*
 * The shell container.
 *
 * This rule used to centre a single card with `max-inline-size: 60rem` and
 * `margin-inline: auto`, from before there was a shell. When the navigation
 * and the content pane moved inside it, nobody removed it, and the result was
 * invisible for two phases: an unconstrained flex item takes its max-content
 * width, so the whole application laid itself out 620px wide inside a 390px
 * phone and `body { overflow: hidden }` quietly cut off the right-hand third.
 * Nothing scrolled, nothing errored, and every measurement of
 * `documentElement.scrollWidth` said 390 because the overflow was clipped.
 *
 * `inline-size: 100%` is what makes the width definite, and it is the whole
 * fix. `min-inline-size: 0` keeps a wide child — the navigation, a table —
 * from pushing it open again instead of scrolling inside itself.
 */
#app {
    display: flex;
    flex-direction: column;
    flex: 1;
    inline-size: 100%;
    min-inline-size: 0;
    overflow: hidden;
}

.card {
    background: var(--panel);
    border: 1px solid var(--card-border);
    border-radius: calc(var(--radius) * 1.5);
    box-shadow: var(--card-shadow-raised);
    /* More room than a page of text needs, which is the point: this is one
       screen of an application rather than an article, and the space is what
       stops it reading as a wall. */
    padding: var(--space-6) 1.5rem 2rem;
}

/* Sections inside a card get room to be separate things. */
.card .section__title {
    margin-block-start: 2rem;
}

.card .section__title:first-of-type {
    margin-block-start: 1.5rem;
}

.card__title {
    margin: 0 0 0.75rem;
    font-size: 1.5rem;
    font-weight: 650;
    letter-spacing: -0.025em;
    line-height: 1.15;
}

.card__intro {
    margin: 0;
    color: var(--text-dim);
}

.eyebrow {
    display: inline-block;
    margin-block-end: 0.75rem;
    color: var(--accent-text);
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0;
}

/*
 * A secondary note.
 *
 * The monospace face is gone. It was here to line numbers up, and it ended up
 * carrying whole paragraphs of explanation — which is how the cardio screen
 * came to explain which watches export which format in a terminal font. Prose
 * is prose; the numbers that genuinely need to line up keep tabular figures,
 * which is the part that was doing the work.
 */
/*
 * A separator block, on the same rhythm as a section heading.
 *
 * It carries a rule above it and it ends a section, so it is doing a heading's
 * job from the other side — and it was doing it at 20 pixels while every
 * heading did it at 24. Measured, that made the distance under a screen's
 * intro 20 on two screens and 24 on two others.
 */
.meta {
    margin-block-start: var(--space-6);
    padding-block-start: var(--space-4);
    border-block-start: 1px solid var(--border);
    color: var(--text-dim);
    font-size: 0.875rem;
    line-height: 1.5;
    font-variant-numeric: tabular-nums;
}

/* --- shell --- */

/*
 * The shell does not scroll; only the content pane does. That is what keeps
 * navigation reachable with a thumb while the list of sets underneath is
 * halfway down.
 */
html,
body {
    block-size: 100dvb;
    overflow: hidden;
}

body {
    display: flex;
    flex-direction: column;
}

/*
 * The cheapest accessibility win there is, and the one most often missing:
 * without it a keyboard user tabs through the whole navigation on every
 * single screen.
 */
.skip-link {
    position: absolute;
    inset-inline-start: -9999px;
    z-index: 100;
    padding: 0.75rem 1rem;
    background: var(--panel);
    color: var(--accent-text);
    border-radius: var(--radius);
    box-shadow: var(--shadow-md);
}

.skip-link:focus {
    inset-inline-start: 1rem;
    inset-block-start: 1rem;
}

.offline-banner {
    padding: var(--space-2) 1rem;
    background: var(--warning-color);
    color: #000;
    font-size: 0.8125rem;
    text-align: center;
}

/*
 * The navigation, in two shapes.
 *
 * On a phone it is a bar across the bottom: four screens somebody opens every
 * day, and a More button for the rest. Bottom rather than top because that is
 * where a thumb reaches, which matters most in the place this application is
 * used standing up between sets.
 *
 * On a wide screen it is a sidebar with everything visible. There the room
 * exists, and hiding half of it behind a button would be hiding it for no
 * reason.
 *
 * What this replaced was nine tabs in a strip that scrolled sideways. It
 * worked, in the sense that everything was reachable; landing on Cardio put
 * the highlight past the right-hand edge, and nothing on screen said the last
 * three tabs were there at all.
 */
.nav {
    position: fixed;
    inset-block-end: 0;
    inset-inline: 0;
    z-index: 40;

    display: flex;
    justify-content: space-around;
    align-items: stretch;

    /*
     * Edge to edge, which is what both platforms actually draw.
     *
     * It floated: margins at the sides, a card's corner radius and a drop
     * shadow, on the reasoning that an object reads less like a website's
     * footer than a bar welded to the bottom. That is a real style and some
     * applications use it, but it is not the standard one — iOS gives a tab
     * bar the full width with a hairline and a translucent material above the
     * content, and Material 3's navigation bar does the same with a tinted
     * pill behind the current icon. The floating version reads as a widget
     * sitting on the page rather than as the edge of the application.
     *
     * The hairline does the work the shadow was doing: it separates the bar
     * from what scrolls under it without pretending to be a separate object.
     */
    background: var(--panel);
    border-block-start: 1px solid var(--border);

    /* The home indicator on an iPhone sits over the bottom of the screen.
       Without this the last row of tabs is under it and half of each one
       cannot be pressed. */
    padding-block-end: env(safe-area-inset-bottom, 0px);
}

/* Everything that is not a daily screen lives in the sheet on a phone. */
.nav > [data-daily="no"] {
    display: none;
}

/*
 * A tab, whether it is a link or a button.
 *
 * "More" is a button because it opens a sheet rather than going anywhere, and
 * a button that is not told otherwise wears the browser's own chrome: a grey
 * box with a border, in a row of five plain tabs. Third time this stylesheet
 * has been caught by a default it never overrode, so the reset is on the
 * shared rule rather than on the one control.
 */
.nav__link {
    position: relative;
    border: 0;
    /* A button also brings its own horizontal padding, which made this tab
       twelve pixels wider than the four beside it. */
    padding-inline: 0;
    background: none;
    font: inherit;
    color: inherit;
    cursor: pointer;

    /* 48px: anything used mid-set has to be reachable with a thumb, on a
       phone, with wet hands. */
    min-block-size: var(--touch-target);
    flex: 1;

    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.15rem;
    padding-block: var(--space-2);

    color: var(--text-dim);
    text-decoration: none;
    /* Sentence case, not shouted. Uppercase micro-labels are a desktop
       convention; a phone tab says "Today", not "TODAY". */
    font-size: 0.6875rem;
    font-weight: 550;
    letter-spacing: 0;
    text-align: center;
    /* A label that wraps to two lines makes one tab taller than its
       neighbours. Ellipsis keeps the row even, and the icon still says which
       one it is. */
    white-space: nowrap;
    overflow: hidden;
    transition: color 0.2s var(--ease-standard), background 0.2s var(--ease-standard);
}

/*
 * The focus ring is drawn inside these, not outside.
 *
 * A tab sits flush against the bottom of the viewport, and the global ring is
 * two pixels outside the box with a two-pixel offset — four pixels past the
 * edge of the screen, where the shell clips it. A keyboard user tabbing into
 * the navigation saw three sides of a rectangle. Measured: the ring reached
 * 848 on an 844-pixel viewport.
 */
.nav__link:focus-visible,
/*
 * A plate behind each tab icon.
 *
 * It is invisible until the tab is current, and then it is the thing that
 * lights up. Without it the tint has nothing to fill and the only way to mark
 * the tab is to recolour a thin stroke, which at this size is barely visible.
 */
.nav__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 2.1rem;
    block-size: 1.65rem;
    border-radius: var(--radius-pill);
    transition: background 0.18s var(--ease-standard), color 0.18s var(--ease-standard);
}

.nav__label {
    max-inline-size: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
}

/*
 * An icon.
 *
 * Filled, so the shape itself takes the colour. The stroke properties are
 * cleared explicitly because the old set was stroked and a leftover rule
 * anywhere would put a hairline around a solid shape.
 */
.icon {
    flex: none;
    inline-size: 22px;
    block-size: 22px;
    fill: currentColor;
    stroke: none;
}

.nav__link:hover {
    color: var(--text-main);
    background: rgba(var(--accent-rgb), 0.08);
}

.nav__link--current {
    color: var(--accent-text);
}

/*
 * The current tab is marked by tinting its icon, not by a rule above it.
 *
 * The rule was drawn when the bar was welded to the bottom edge and needed
 * something to say "you are here". Now that the bar floats, a line across the
 * top of a rounded object reads as a seam. Every phone application marks the
 * current tab by lighting it up, and that is what somebody's eye is looking
 * for.
 */
.nav__link--current .nav__icon {
    background: rgba(var(--accent-rgb), 0.14);
    color: var(--accent);
}

.content {
    flex: 1;
    overflow-y: auto;
    /*
     * No padding at the top, and the reason is the header.
     *
     * This is the scroll container, and a `position: sticky` bar inside it
     * stops at the container's *padding* edge, not at the edge you can see. So
     * a scroller with 1.5rem of top padding parks every screen header 24px
     * down and leaves a band above it that the content scrolls through in
     * full focus — a strip of chips and buttons sliding over the blurred bar,
     * which is what "se ve el contenido por su parte de atrás" was.
     *
     * The space still exists; it just belongs to the screen rather than to the
     * scroller, so a screen that opens with a header starts flush and one that
     * opens with a title gets its air back. See `.screen` below.
     */
    padding-block-start: 0;
    /* The inline-end padding accounts for a phone's home indicator. */
    padding-inline: var(--gutter);
    /* Clear of the fixed bar, and of the home indicator underneath it.
       Without this the last thing on every screen is behind the tabs, which
       on the session screen is the button that logs a set. */
    padding-block-end: calc(1.5rem + var(--touch-target) + env(safe-area-inset-bottom, 0px));
}

.content:focus {
    /* Focus moves here on navigation so screen readers announce the new page;
       it should not draw a ring for that. */
    outline: none;
}

.content > .card {
    max-inline-size: 44rem;
    margin-inline: auto;
}

.loading {
    padding: 2rem;
    color: var(--text-dim);
    text-align: center;
}

/* --- forms --- */

.form {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    margin-block-start: 1.25rem;
}

/*
 * A label belongs to the field under it, and the spacing has to say so.
 *
 * Eight pixels from its own input and none at all from the field above meant
 * every label sat closer to somebody else's box than to its own — "Name" was
 * touching the username field it had nothing to do with. Proximity is how
 * grouping is read before a word of it, and this had it backwards.
 */
.field {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.field + .field {
    margin-block-start: var(--space-4);
}

.label {
    font-size: 0.875rem;
    font-weight: 700;
    letter-spacing: 0;
    color: var(--text-dim);
}

.input {
    min-block-size: var(--touch-target);
    padding-inline: var(--space-3);
    border: 1px solid var(--card-border);
    border-radius: var(--radius);
    background: var(--surface-solid);
    color: var(--text-main);
    font-family: inherit;
    font-size: 1rem; /* 16px, or iOS zooms the page on focus. */
}

.input:focus-visible {
    border-color: var(--accent);
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

/*
 * A pill with a fill, not a bordered box with shouted text.
 *
 * The old one was desktop chrome: a hairline border, an eight-pixel corner and
 * UPPERCASE LETTERSPACED text. Every one of those three is a giveaway. A phone
 * application's button is a filled pill with its label in sentence case, and
 * the primary one is solid rather than outlined — an outline reads as the
 * secondary choice on every platform, which is the opposite of what the main
 * action on a screen should look like.
 */
.button {
    min-block-size: var(--touch-target);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    padding-inline: var(--space-5);
    border: 0;
    border-radius: var(--radius-pill);
    /*
     * A tint rather than the canvas colour. The quiet button used to be
     * painted --canvas, which worked while every screen was a white panel and
     * became invisible the moment the panel went away: a canvas-coloured
     * button on the canvas is nothing at all. A tint of the dim text colour
     * reads on both surfaces and in both themes.
     */
    background: color-mix(in srgb, var(--text-dim) 14%, transparent);
    color: var(--text-main);
    font-family: inherit;
    font-size: 0.9375rem;
    font-weight: 600;
    letter-spacing: -0.01em;
    text-decoration: none;
    cursor: pointer;
    transition: transform 0.12s var(--ease-out), background 0.2s var(--ease-out),
        opacity 0.2s var(--ease-out);
}

/* Something moves when a thumb lands on it. Under the reduced-motion rule
   like everything else. */
.button:active:not(:disabled) {
    transform: scale(0.975);
}

.button:hover:not(:disabled) {
    border-color: var(--accent);
    color: var(--accent-text);
}

.button:active:not(:disabled) {
    transform: translateY(1px);
}

.button:disabled {
    opacity: 0.55;
    cursor: not-allowed;
}

/*
 * Solid, and white on the accent.
 *
 * Measured: white on #e8520e is 3.9:1, which passes AA at this size — the
 * button's label is 15 px at weight 600, above the large-text threshold. It is
 * the one place the pure accent carries text, and it is why the label is not
 * smaller than this.
 */
.button--primary {
    background: var(--accent);
    color: #ffffff;
}

.button--primary:hover:not(:disabled) {
    background: var(--accent-alt);
}

.button--danger {
    background: rgba(220, 38, 38, 0.12);
    color: var(--danger-color);
}

/* A button that fills its line, which is what a phone does with the one
   action a screen is about. */
.button--wide {
    inline-size: 100%;
}

.notice {
    padding: 0.75rem 1rem;
    border-inline-start: 4px solid var(--info-color);
    border-radius: calc(var(--radius) - 4px);
    background: var(--surface);
    font-size: 0.875rem;
}

.notice--error {
    border-inline-start-color: var(--danger-color);
}

.meta dt {
    margin-block-start: 0.75rem;
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0;
    color: var(--text-dim);
}

.meta dd {
    margin: 0.2rem 0 0;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

/* --- desktop --- */

/*
 * Secondary does not mean degraded. On a wide screen the navigation becomes a
 * side rail, which is where a pointer expects it and which stops a five-item
 * bar from stretching across a 27-inch monitor.
 */
@media (min-width: 60rem) {
    #app {
        flex-direction: row;
    }

    /* A sidebar with everything on it. There is room, so nothing is hidden. */
    .nav {
        position: static;
        flex-direction: column;
        justify-content: flex-start;
        inline-size: 13rem;
        padding-block: 1rem;
        padding-inline: 0.75rem;
        padding-block-end: 1rem;
        border-block-start: none;
        border-inline-end: 1px solid var(--border);
    }

    .nav > [data-daily="no"] {
        display: flex;
    }

        .nav__link {
        flex: none;
        flex-direction: row;
        justify-content: flex-start;
        gap: var(--space-3);
        padding-inline: 0.75rem;
        border-radius: var(--radius);
        font-size: 0.8125rem;
        text-align: start;
    }

    .nav__link--current {
        background: rgba(var(--accent-rgb), 0.12);
    }

    /* The "you are here" bar belongs at the bottom of a screen, not beside a
       filled row. */
    .nav__link--current::before {
        display: none;
    }

    .content {
        padding-block-end: 1.5rem;
    }

    .offline-banner {
        position: fixed;
        inset-block-start: 0;
        inset-inline: 0;
        z-index: 50;
    }
}

/*
 * A section's name.
 *
 * It used to be a dim 12px upper-case run-in, which is a form's legend. It is
 * a heading: full size, full colour, sentence case, and it is what somebody
 * scanning the screen stops on.
 */
/*
 * One rhythm for a section heading, whichever component draws it.
 *
 * There are two — a bare `.section__title` and the `.sectionhead` that carries
 * an explanation dot and an action — and they had different margins: 32 above
 * and 8 below against 24 and 12. Measured across fifteen screens, that made
 * the same relationship arrive at 12, 14, 24 and 32 pixels depending on which
 * one a screen happened to use, which is not a decision anybody made.
 *
 * 24 above and 12 below: enough to separate two sections, close enough that
 * the heading belongs to what is under it rather than floating between.
 */
.section__title {
    margin-block: var(--space-6) var(--space-3);
    font-size: 1.125rem;
    font-weight: 700;
    letter-spacing: -0.015em;
    color: var(--text-main);
}

/* --- the live session --- */

/*
 * Everything here is sized for a thumb, standing up, with chalk on the hands.
 * The 48px floor is a floor, not a target: the controls used between sets are
 * deliberately larger.
 */

/*
 * A row of actions fills the width.
 *
 * Left-aligned they leave a gap on the trailing side that a phone never has —
 * measured at 55 pixels on the home card and 41 on cardio — and the eye reads
 * that emptiness as something missing. Equal halves also stop the secondary
 * action from looking like an afterthought stuck onto the primary.
 */
.row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-block-start: var(--space-4);
}

.row > .button {
    flex: 1 1 auto;
    justify-content: center;
}

/*
 * The rest timer is pinned, because the question it answers — "can I go
 * again?" — is asked while looking at something else.
 */
/*
 * The rest, on the whole screen.
 *
 * It was a strip across the top of a card full of other things. Resting is not
 * a status bar: it is a minute and a half of standing still holding a phone,
 * and the only moment in a session with nothing to do and something to read.
 *
 * Fixed rather than in the flow, so the card underneath keeps everything typed
 * into it — covering it and destroying it are very different things, and this
 * project has already lost a number pad once to the second.
 */
.rest {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    padding: 2rem 1.5rem;
    background: var(--canvas);
    text-align: center;
}

.rest[hidden] {
    display: none;
}

.rest .mascot {
    inline-size: 7rem;
    block-size: 7rem;
}

/*
 * The countdown, big enough to read at arm's length.
 *
 * Tabular figures, or it jitters as the digits change width and keeps pulling
 * the eye back to something that is only counting down.
 */
.rest__clock {
    margin: 0;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: 3.5rem;
    font-weight: 650;
    line-height: 1;
    color: var(--accent-text);
}

.rest__words {
    margin: 0;
    max-inline-size: 22rem;
    color: var(--text-dim);
    line-height: 1.55;
}

.rest__actions {
    display: flex;
    gap: 0.75rem;
    margin-block-start: 0.5rem;
}

/*
 * Where the exercise is in the session.
 *
 * Two arrows and a count, and the count is the reason the arrows are readable:
 * "2 of 5" says both where you are and that there are five, which a pair of
 * chevrons on their own cannot.
 */
.train__position {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    margin-block-end: 0.75rem;
}

.train__step {
    inline-size: var(--touch-target);
    block-size: var(--touch-target);
    border: none;
    border-radius: 50%;
    background: var(--surface-solid);
    color: var(--text-main);
    font-size: 1.25rem;
    line-height: 1;
    cursor: pointer;
}

.train__step[disabled] {
    opacity: 0.35;
    cursor: default;
}

.train__step:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.train__count {
    font-variant-numeric: tabular-nums;
    color: var(--text-dim);
    font-size: 0.9375rem;
}

/*
 * The question at the end of an exercise.
 *
 * Above the log button rather than replacing it: somebody who wants a fourth
 * set should not have to dismiss a panel to reach the thing they were already
 * using. Both ways out, neither hidden.
 */
/*
 * The name of the exercise, and everything else it can do.
 *
 * The card carried seven buttons at once — log, warm-up, swap, add another,
 * finish the session, and the two of the end-of-exercise question. Between
 * sets, one-handed, that is a web page. The dots hold what is neither urgent
 * nor frequent.
 */
.exercise__head,
.train__title {
    display: flex;
    align-items: start;
    justify-content: space-between;
    gap: 0.75rem;
}

/*
 * The two of them look the same, because they are the same control.
 *
 * The card's one was `--surface-solid`, which in the light theme is white —
 * on a white card that is an invisible button and three dots floating beside
 * the exercise name. Next to the header's, which sits on the canvas and
 * therefore had a visible disc, they read as two different things.
 *
 * The canvas colour is the one that shows on both: a quiet disc on the card,
 * and a quiet disc on the page.
 */
.exercise__more_button,
.train__more {
    flex-shrink: 0;
    inline-size: var(--touch-target);
    block-size: var(--touch-target);
    border: none;
    border-radius: 50%;
    color: var(--text-dim);
    font-size: 1.125rem;
    line-height: 1;
    letter-spacing: 0.05em;
    cursor: pointer;
}

.train__more {
    background: var(--panel);
}

.exercise__more_button {
    background: var(--canvas);
}

.exercise__more_button:focus-visible,
.train__more:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.exercise__actions[hidden] {
    display: none;
}

.exercise__finished {
    margin-block: 0.75rem;
    padding: var(--space-3);
    border-radius: var(--radius);
    background: rgba(var(--accent-rgb), 0.1);
}

.exercise__finished[hidden] {
    display: none;
}

.exercise__finished_note {
    margin: 0 0 var(--space-3);
    font-weight: 600;
}

.timer__button {
    min-block-size: var(--touch-target);
    min-inline-size: 3.5rem;
    border: 1px solid var(--accent);
    border-radius: var(--radius);
    background: transparent;
    color: var(--accent-text);
    font-family: var(--font-mono);
    font-size: 0.875rem;
    cursor: pointer;
}

.train__header {
    margin-block-end: 1.25rem;
}

.exercise {
    margin-block-end: 1.5rem;
    padding: 1.25rem;
    border: 1px solid var(--card-border);
    border-radius: calc(var(--radius) * 1.5);
    background: var(--panel);
    box-shadow: var(--card-shadow);
}

.exercise__name {
    margin: 0;
    font-size: 1.125rem;
    font-weight: 650;
    letter-spacing: -0.01em;
}

.exercise__target {
    margin: 0.2rem 0 0;
    color: var(--text-dim);
    font-size: 0.8125rem;
}

/*
 * Last time sits above the input, because it is what the number is chosen
 * against. Putting it anywhere else recreates the lookup this replaces.
 */
.exercise__lasttime {
    margin: 0.75rem 0 0;
    padding: 0.5rem 0.75rem;
    border-inline-start: 3px solid var(--accent);
    background: rgba(var(--accent-rgb), 0.06);
    border-radius: calc(var(--radius) - 4px);
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: 0.8125rem;
}

.sets {
    margin: 1rem 0 0;
    padding: 0;
    list-style: none;
}

.sets__row {
    border-block-end: 1px solid var(--border);
    font-variant-numeric: tabular-nums;
}

/*
 * The whole row is the target.
 *
 * A set logged wrong used to be permanent on this screen, and the fix must not
 * be a pencil and a bin on every line — five sets would be ten more controls on
 * a card already carrying too many. So the line itself is the button: nothing
 * new appears, and the thing you want to change is the thing you touch.
 *
 * 48px because it is used mid-set with one thumb, which is the rule for every
 * control on this screen and the reason it is written here rather than left to
 * the text's own height.
 */
.sets__touch {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    inline-size: 100%;
    min-block-size: var(--touch-target);
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    text-align: start;
    cursor: pointer;
}

/* The value takes the slack so the chevron is pinned to the end of the line,
   which is where every phone puts it and where a thumb expects it. */
.sets__touch .sets__value {
    flex: 1 1 auto;
    min-inline-size: 0;
}

/* "Further in" is leftwards in Arabic, Farsi and Urdu. The rows elsewhere in
   the application flip theirs the same way. */
[dir="rtl"] .sets__touch .rowx__chevron {
    transform: scaleX(-1);
}

/* Correct and delete, side by side while they fit and stacked when they do not
   — with a gap either way, because one of them is destructive. */
.sets__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.sets__touch:active {
    background: var(--surface-sunken, rgb(var(--accent-rgb) / 0.06));
}

.sets__number {
    inline-size: 1.75rem;
    color: var(--text-dim);
    font-family: var(--font-mono);
    font-size: 0.75rem;
}

.sets__value {
    font-family: var(--font-mono);
    font-size: 0.9375rem;
}

/*
 * What to put on the bar, under the number it belongs to.
 *
 * Quiet on purpose: it is an answer to a question somebody has while their
 * hands are on the plates, not a thing to read. The weight is what they came
 * for and this sits under it.
 */
.exercise__loading {
    margin: var(--space-2) 0 0;
    font-family: var(--font-mono);
    font-size: 0.8125rem;
    color: var(--text-dim);
    text-align: center;
}

/* One rung of the warm-up: the set to do, and what it is made of, in one
   target the width of the sheet. */
.rung {
    inline-size: 100%;
}

.rung + .rung {
    margin-block-start: var(--space-2);
}

.rung__text {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-1);
}

.rung__plates {
    font-size: 0.8125rem;
    font-weight: 500;
    opacity: 0.7;
}

/* Done, and still readable: a ramp that greys out to nothing loses the record
   of what was just lifted. */
.rung--done {
    opacity: 0.45;
}

/* Warm-ups are visibly not the work. They count for nothing and should not
   read as if they do. */
.sets__row--warmup .sets__value {
    color: var(--text-dim);
}

/*
 * Weight above repetitions, always, and both the same width.
 *
 * They used to sit side by side above 30rem in columns of 1.4fr and 1fr —
 * deliberately unequal, to give the weight more room — and on a 488-pixel
 * phone that is exactly what it looked like: two controls of two different
 * sizes, which reads as a mistake rather than as a priority. Worse, the
 * narrower one left about fifty pixels for the number, and the number is the
 * thing being read at arm's length.
 *
 * There is no width at which two of these belong on one line on a phone. The
 * card is capped instead, so a desktop gets a control the size of a control
 * rather than one stretched across a monitor.
 */
.exercise__entry {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1rem;
    margin-block-start: 1rem;
    max-inline-size: 26rem;
}

/*
 * The stepper: minus, the number, plus — in that order, on one line.
 *
 * It was a column: the number, and under it a grid of "+0.5 −0.5 +1 −1". Four
 * targets for one job, and the two step sizes had to be chosen between before
 * anything could be nudged. Every phone platform puts one minus and one plus
 * either side of the value, and the reason is that the hand already knows
 * where they are.
 */
.pad {
    display: flex;
    align-items: stretch;
    gap: 0.5rem;
}

/*
 * Large, monospaced and tabular. This is the number somebody reads at arm's
 * length while holding a bar.
 */
.pad__display {
    /*
     * Border box, and allowed to shrink.
     *
     * `inline-size: 100%` on a flex item whose padding is added outside the
     * box makes it wider than the space it was given, and a flex item's
     * default `min-width: auto` refuses to shrink back — so the number
     * overflowed its own field and came out clipped. The same border-box
     * mistake as the tooltip, in a different shape.
     */
    box-sizing: border-box;
    flex: 1 1 auto;
    min-inline-size: 0;
    min-block-size: 3.5rem;
    padding-inline: 0.75rem;
    border: 1px solid var(--card-border);
    border-radius: var(--radius);
    background: var(--surface-solid);
    color: var(--text-main);
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: 1.75rem;
    font-weight: 650;
    text-align: center;
    inline-size: 100%;
}

.pad__display:focus-visible {
    border-color: var(--accent);
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

/*
 * Square, and as tall as the number beside it.
 *
 * `flex: 0 0 auto` with a fixed inline size rather than a share of the row:
 * the number is what grows when the sheet is wider, because the number is
 * what somebody reads at arm's length while holding a bar.
 */
.pad__step {
    flex: 0 0 auto;
    inline-size: 3.5rem;
    min-block-size: var(--touch-target);
    border: 1px solid var(--card-border);
    border-radius: var(--radius);
    background: var(--surface-solid);
    color: var(--text-main);
    font-size: 1.5rem;
    font-weight: 600;
    line-height: 1;
    cursor: pointer;
    /* A held button repeats; the browser must not select the glyph or offer
       a magnifier instead. */
    user-select: none;
    touch-action: manipulation;
    transition:
        border-color 0.15s var(--ease-standard),
        background-color 0.15s var(--ease-standard);
}

.pad__step:hover {
    border-color: var(--accent);
}

.pad__step:active {
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 14%, var(--surface-solid));
}

.pad__step:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

/*
 * The keypad.
 *
 * Three columns, the shape of every dialler and every calculator, so a thumb
 * that has used a phone already knows where the 7 is. Keys are square-ish and
 * a share of the width rather than a fixed size, because the narrowest phone
 * still sold is 320 pixels and three columns of a fixed width is how a keypad
 * ends up scrolling sideways.
 */
.pad__host {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.pad__keys {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-2);
}

.pad__keys[hidden] {
    display: none;
}

.pad__key {
    min-block-size: 3.25rem;
    border: 1px solid var(--card-border);
    border-radius: var(--radius);
    background: var(--surface-solid);
    color: var(--text-main);
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: 1.375rem;
    font-weight: 600;
    cursor: pointer;
    user-select: none;
    touch-action: manipulation;
    transition:
        border-color 0.15s var(--ease-standard),
        background-color 0.15s var(--ease-standard);
}

.pad__key:active {
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 14%, var(--surface-solid));
}

.pad__key:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

/*
 * The gap where a separator key would be on a field that holds no decimals.
 *
 * Present rather than absent so the zero stays in the middle column. A grid
 * that reflows when the field changes kind is a keypad whose keys move.
 */
.pad__key--blank {
    border-color: transparent;
    background: none;
    pointer-events: none;
}

.pad__key--rub {
    font-family: var(--font-sans);
    font-size: 1.125rem;
}

.exercise__suggestion {
    margin: 0.75rem 0 0;
    color: var(--text-dim);
    font-size: 0.75rem;
}

.exercise__actions {
    display: flex;
    gap: var(--space-3);
    margin-block-start: 1rem;
}

.exercise__log {
    flex: 2;
    /* The primary action of the whole application. It is bigger than the
       floor on purpose. */
    min-block-size: 3.25rem;
}

.exercise__warmup {
    flex: 1;
}

.train__actions {
    margin-block: 2rem 1rem;
}

.train__actions .button {
    inline-size: 100%;
}

.picker {
    margin-block: 1rem;
}

.picker__toggle {
    inline-size: 100%;
}

.picker__panel {
    margin-block-start: 0.75rem;
    padding: 0.75rem;
    border: 1px solid var(--card-border);
    border-radius: var(--radius);
    background: var(--panel);
}

.picker__results {
    margin: var(--space-3) 0 0;
    padding: 0;
    list-style: none;
    max-block-size: 18rem;
    overflow-y: auto;
}

.picker__result {
    inline-size: 100%;
    min-block-size: var(--touch-target);
    padding-inline: 0.75rem;
    border: none;
    border-block-end: 1px solid var(--border);
    background: transparent;
    color: var(--text-main);
    font-family: inherit;
    font-size: 0.9375rem;
    text-align: start;
    cursor: pointer;
}

.picker__result:hover {
    background: rgba(var(--accent-rgb), 0.08);
    color: var(--accent-text);
}

/*
 * What to train today.
 *
 * No box of its own any more. It lives inside a card, and a bordered,
 * tinted panel inside a card is a box inside a box — the thing that made
 * every screen look like a form rather than an application.
 */
/*
 * No gap, because a flex gap adds to every margin under it.
 *
 * The 2.4 pixels here were for the pair at the top — a day name and the line
 * under it — and a gap applies between all of them, so every deliberate margin
 * in this card came out two and a half pixels bigger than it was written:
 * 16 measured 18, 8 measured 10, and nothing was on the scale. The pair keeps
 * its closeness through its own margin instead.
 */
.today {
    display: flex;
    flex-direction: column;
}

.today__day {
    margin: 0;
    font-size: 1.5rem;
    font-weight: 700;
    letter-spacing: -0.03em;
    line-height: 1.15;
}

.today__reason {
    /* Close to the day name above it: they are one thing said in two lines. */
    margin: var(--space-1) 0 0;
    color: var(--text-dim);
    font-size: 0.8125rem;
}

.today .row {
    margin-block-start: var(--space-4);
}

.exercise__more {
    margin-block-start: var(--space-3);
}

.exercise__swap {
    inline-size: 100%;
}

.exercise__substitutes {
    margin-block-start: 0.75rem;
    padding: 0.75rem;
    border: 1px solid var(--card-border);
    border-radius: var(--radius);
}

.feel__row {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    margin-block-start: 1.25rem;
}

.feel__scale {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(2.75rem, 1fr));
    gap: var(--space-2);
}

.feel__option {
    min-block-size: var(--touch-target);
    border: 1px solid var(--card-border);
    border-radius: var(--radius);
    background: var(--surface-solid);
    color: var(--text-main);
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: 0.9375rem;
    cursor: pointer;
}

.feel__option--chosen {
    border-color: var(--accent);
    background: rgba(var(--accent-rgb), 0.15);
    color: var(--accent-text);
}

/* --- charts --- */

.chart {
    margin: 1rem 0 0;
}

.chart__row {
    display: grid;
    grid-template-columns: 7.5rem 1fr 4.5rem;
    align-items: center;
    gap: 0.5rem;
    padding-block: 0.25rem;
}

.chart__label {
    font-size: 0.75rem;
    color: var(--text-dim);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.chart__track {
    block-size: 0.75rem;
    border-radius: 999px;
    background: var(--border);
    overflow: hidden;
}

.chart__bar {
    display: block;
    block-size: 100%;
    background: var(--accent);
    border-radius: 999px;
}

.chart__value {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: 0.75rem;
    text-align: end;
}

.chart__caption {
    margin-block-start: 0.5rem;
    color: var(--text-dim);
    font-size: 0.6875rem;
    letter-spacing: 0;
}

/*
 * A trend with its axes.
 *
 * The plot and the scale sit side by side, so the numbers are outside the
 * drawing rather than floating on top of it, and the dates run underneath.
 */
.chart__plot {
    display: flex;
    align-items: stretch;
    gap: 0.5rem;
}

/*
 * The drawing, at a fixed aspect.
 *
 * Not stretched. A viewBox squashed to fill its box turns every circle into an
 * ellipse and draws a stroke at two different weights depending on its
 * direction — which reads as "made in a hurry" without anybody being able to
 * say why. Fixing the ratio costs a line and makes the dots round.
 */
.plot {
    flex: 1;
    min-inline-size: 0;
    aspect-ratio: 100 / 34;
    overflow: visible;
}

.plot__rule {
    stroke: var(--text-dim);
    stroke-width: 1;
    stroke-dasharray: 1 3;
    opacity: 0.45;
    vector-effect: non-scaling-stroke;
}

.plot__line {
    fill: none;
    stroke: var(--accent);
    stroke-width: 2;
    vector-effect: non-scaling-stroke;
    stroke-linejoin: round;
    stroke-linecap: round;
}

.plot__area {
    fill: rgba(var(--accent-rgb), 0.14);
    stroke: none;
}

/*
 * The readings the line is made of.
 *
 * Stroked in the panel colour so each one punches a hole in the line and reads
 * as a joint rather than a bead sitting beside it.
 */
.plot__dot {
    fill: var(--panel);
    stroke: var(--accent);
    stroke-width: 1.5;
    vector-effect: non-scaling-stroke;
}

/* Where you are now: filled, and slightly bigger. */
.plot__now {
    fill: var(--accent);
    stroke: var(--panel);
    stroke-width: 1.5;
    vector-effect: non-scaling-stroke;
}

/*
 * The scale, down the right.
 *
 * Pushed apart by the box rather than positioned by hand, which lands the
 * three labels on the three rules without a coordinate anywhere in the code.
 */
.chart__scale {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    align-items: flex-start;
    font-size: 0.6875rem;
    line-height: 1;
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* The dates, under the plot and aligned to the readings above them. */
.chart__axis {
    display: grid;
    margin-block-start: var(--space-2);
    /* The scale on the right takes its width out of the plot, so the ticks
       have to stop where the plot does or every one of them sits slightly to
       the left of the point it names. */
    padding-inline-end: var(--space-10);
    font-size: 0.6875rem;
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
}

.chart__tick {
    text-align: center;
    white-space: nowrap;
    overflow: visible;
}

.chart__tick--now {
    color: var(--text-main);
    font-weight: 700;
}

/*
 * A list of readings: the sets of one exercise, the records it holds, the
 * heart-rate zones.
 *
 * One card with hairlines between the lines, the same shape the other lists
 * on this application use. Bare on the canvas, separated by rules, it was a
 * printout — and it is the shape the eye has to recognise instantly, because
 * on the exercise screen it appears three times in a row with a heading over
 * each and nothing else telling them apart.
 */
.plain-list {
    margin-block: var(--space-3);
    padding: var(--space-1) 1rem;
    list-style: none;
    background: var(--panel);
    border-radius: var(--radius-card);
    box-shadow: var(--card-shadow-raised);
}

.plain-list li {
    padding-block: 0.5rem;
    border-block-end: 1px solid var(--border);
}

/* The last line sits on the card's own edge, so its rule would be a hairline
   floating just inside the bottom of the card. */
.plain-list li:last-child {
    border-block-end: none;
}

/*
 * Two photographs, side by side.
 *
 * Side by side even on a phone: this comparison is the whole reason the
 * pictures exist, and stacking them puts months of difference on two screens
 * where the eye cannot hold both. Two narrow columns of a portrait photograph
 * are legible on a 390 px screen; the same photograph twice its width and
 * scrolled past is not.
 */
.photo-pair {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.5rem;
    margin-block-start: 0.75rem;
}

.photo-dates {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.5rem;
}

.photo {
    margin: 0;
    min-inline-size: 0;
}

.photo__image {
    inline-size: 100%;
    block-size: auto;
    border-radius: var(--radius);
    border: 1px solid var(--border);
    /* A body photograph is portrait; a landscape one would otherwise sit in a
       band of empty space and make the pair unreadable. */
    aspect-ratio: 3 / 4;
    object-fit: cover;
    background: var(--canvas);
}

/* Stacked, not a row. Each column is about 145 px on a phone, which is not
   enough for a date and a button side by side: the date wrapped to
   "2026-" and "03-01" and stopped reading as a date. */
.photo__caption {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-2);
    margin-block-start: var(--space-2);
    text-align: center;
}

.photo__caption .grant__facts {
    margin: 0;
    white-space: nowrap;
}

/* Nothing about a body photograph belongs on paper by accident. */
@media print {
    .photo-pair,
    .photo-dates {
        display: none;
    }
}

/*
 * A secret meant to be typed by hand.
 *
 * Monospace and wide-spaced because somebody is copying it character by
 * character into another device, and the pairs that get confused doing that —
 * 0 and O, 1 and l — are the ones this needs to keep apart. Selectable on
 * purpose: on a desktop the way to move it is to select it.
 */
.secret {
    margin: 0.5rem 0 1rem;
    padding: var(--space-2) 0.75rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--canvas);
    color: var(--text-main);
    font-family: var(--font-mono);
    font-size: 0.9375rem;
    letter-spacing: 0.08em;
    /* Wrap between the groups, never inside one. Breaking anywhere split
       "V662 HRDH" across two lines as "V662 H" and "RDH", which is exactly the
       kind of thing that gets typed wrong. */
    word-break: keep-all;
    overflow-wrap: break-word;
    user-select: all;
}

/*
 * A link that reads as text.
 *
 * Used on exercise names inside a list of sets and inside a routine, where
 * every second line would otherwise be underlined and the page would look like
 * a search result. The underline appears on hover and focus, which is when
 * somebody is asking whether it is a link.
 */
.plain-link {
    color: inherit;
    text-decoration: none;
    border-block-end: 1px dotted var(--border);
}

.plain-link:hover,
.plain-link:focus-visible {
    color: var(--accent-text);
    text-decoration: underline;
}

/*
 * A number that is the answer.
 *
 * The weight of a set, a week's load, a body weight: the figure somebody came
 * to the screen to see. At body-text size, next to its own label, every screen
 * became a uniform grey wall that had to be read rather than seen. Tabular
 * figures always, so a column lines up and a changing one does not jump the
 * layout sideways.
 */
.figure {
    display: block;
    font-size: var(--font-size-figure);
    font-weight: 650;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
}

.figure--display {
    font-size: var(--font-size-display);
}

.figure__unit {
    font-size: 0.9375rem;
    font-weight: 500;
    color: var(--text-dim);
    letter-spacing: 0;
}

/*
 * "≈ 1.3 croissants".
 *
 * The icon and the words on one line, at the size of ordinary text rather than
 * a headline. It is a footnote about scale, and making it big would make it
 * the point of the screen, which it is not.
 */
.equivalent {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0.75rem 0 0;
    color: var(--text-dim);
    font-size: 0.875rem;
}

.equivalent .icon {
    color: var(--accent);
    flex-shrink: 0;
}

/*
 * The row of pictures, with its explanation dot on the far side.
 *
 * The dot belongs to the row rather than to the card, so it sits on the same
 * line and at the end of it — which is also where every other explanation in
 * this application lives, beside the thing it explains and never under it.
 * Here it carries more than usual: the row deliberately says no number at all,
 * so the dot is the only place the figures exist.
 */
.equivalent__row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
}

.equivalent__row .equivalent {
    margin-block-start: 0;
    min-inline-size: 0;
    /*
     * Ten pictures is the longest row this can produce, and on the narrowest
     * phone that has to wrap rather than push the dot off the edge. They stay
     * tight together because they are one quantity, not a list.
     */
    flex-wrap: wrap;
    gap: 0.125rem;
}

/*
 * Big enough to be the statement rather than a mark beside one.
 *
 * At the body size a single picture read as a stray glyph in the corner —
 * decoration, not information. This is the only thing this line says, so it
 * gets the size of a thing being said.
 */
.equivalent .food-icon {
    font-size: 1.625rem;
    line-height: 1;
}

/*
 * What each picture is worth, inside the explanation sheet.
 *
 * A list and not a table element: three columns of one line each, where the
 * figure is the thing being looked up and belongs at the end where the eye
 * can run down it.
 */
.foodtable {
    margin: var(--space-4) 0 0;
    padding: 0;
    list-style: none;
}

.foodtable__row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding-block: var(--space-2);
    border-block-start: 1px solid var(--card-border);
}

.foodtable__row:first-child {
    border-block-start: 0;
}

.foodtable__icon {
    font-size: 1.25rem;
    line-height: 1;
    flex-shrink: 0;
}

.foodtable__name {
    flex: 1;
    min-inline-size: 0;
}

.foodtable__kcal {
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
    /* A figure keeps its own reading order inside an Arabic sheet. */
    unicode-bidi: isolate;
}

/*
 * The path.
 *
 * The first thing on the home screen, so it gets the room to be read at a
 * glance: a wide chart, then the answer in a sentence somebody can take in
 * without stopping.
 */
.home__path {
    margin-block: 1rem 1.5rem;
}

/*
 * The answer, at the size of an answer.
 *
 * A sentence rather than a number, so the tracking is not as tight as a
 * figure's — but the same weight, because it is the one thing on this card
 * somebody is here to read.
 */
.path__state {
    margin: var(--space-4) 0 0;
    font-size: 1.375rem;
    font-weight: 700;
    letter-spacing: -0.025em;
    line-height: 1.2;
    color: var(--text-main);
}

/* The numbers behind the sentence. Small, and the text alternative to the
   chart for anybody who cannot see it. */
.path__summary {
    margin: var(--space-2) 0 0;
    font-size: 0.8125rem;
    line-height: 1.4;
    color: var(--text-dim);
}

/* The chart's own caption is the summary, which is now printed under the
   answer instead. Kept in the markup for screen readers, hidden from the eye. */
.chart--band .chart__caption {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

.path--empty .path__state {
    font-size: 1rem;
    font-weight: 500;
    color: var(--text-dim);
}

/*
 * The band chart.
 *
 * The band is the accent at low opacity rather than a second colour: this
 * application has one colour, and a chart that introduces a palette to say
 * "good" and "bad" is a chart making a judgement it was told not to make.
 */
.chart--band {
    margin: 0;
}

.band-chart {
    inline-size: 100%;
    display: block;
    overflow: visible;
    /* The ratio sets the height. A fixed block-size alongside it would win and
       squash the drawing back to where the dots are ellipses again. */
    aspect-ratio: 100 / 40;
}

.band-chart__band {
    fill: rgba(var(--accent-rgb), 0.16);
    stroke: rgba(var(--accent-rgb), 0.35);
    /* The band is drawn in a stretched viewBox, so a plain stroke width would
       be stretched with it. */
    stroke-width: 0.4;
    vector-effect: non-scaling-stroke;
}

.band-chart__line {
    fill: none;
    stroke: var(--text-main);
    stroke-width: 1.6;
    stroke-linejoin: round;
    stroke-linecap: round;
    vector-effect: non-scaling-stroke;
}

.band-chart__now {
    fill: var(--accent);
    stroke: var(--panel);
    stroke-width: 1;
    vector-effect: non-scaling-stroke;
}

/*
 * A row of the routine editor.
 *
 * A grid, not a flex row. As a row the name was flex:1 beside three number
 * inputs that would not shrink, so on a 390 px screen it got about ninety
 * pixels and wrapped one letter per line — "Sentadilla trasera" came out as a
 * vertical column of characters. The name now owns a full-width line and the
 * numbers sit under it, with the remove button spanning both.
 */
.routine-item {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-areas:
        "name    remove"
        "numbers remove";
    gap: var(--space-2) 0.5rem;
    align-items: center;
    padding-block: 0.5rem;
}

.routine-item__name {
    grid-area: name;
    min-inline-size: 0;
    /* Break between words, never inside one. */
    overflow-wrap: break-word;
}

.routine-item__numbers {
    grid-area: numbers;
    display: flex;
    align-items: center;
    gap: 0.2rem;
}

.routine-item > button {
    grid-area: remove;
    align-self: center;
}

.routine-item__times {
    color: var(--text-dim);
    font-family: var(--font-mono);
    font-size: 0.75rem;
}

.input--tiny {
    inline-size: 3.5rem;
    padding-inline: var(--space-2);
    text-align: center;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

/* The day's own header: the name takes the width, its two controls follow. */
.routine-day .grant__head {
    flex-wrap: wrap;
}

.routine-day .grant__head > .input {
    flex: 1 1 12rem;
    min-inline-size: 0;
}

/* One prescribed exercise inside a routine day. */
.plain-list li.item {
    font-size: 0.875rem;
    font-variant-numeric: tabular-nums;
}

/* --- history --- */

/*
 * The month, and the way to the ones either side.
 *
 * A bar rather than a picker: somebody looking back through their training
 * moves one month at a time, and a control that opens every month since they
 * started is a list nobody reads.
 */
.monthbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    margin-block-end: 0.75rem;
}

.monthbar__label {
    font-weight: 650;
    font-size: 1.0625rem;
    /* The month name is a word, the year a number, and in Arabic the two
       would otherwise swap places around each other. */
    unicode-bidi: isolate;
}

.monthbar__step {
    display: flex;
    align-items: center;
    justify-content: center;
    inline-size: var(--touch-target);
    block-size: var(--touch-target);
    border-radius: 50%;
    background: var(--surface-solid);
    color: var(--text-main);
    font-size: 1.25rem;
    line-height: 1;
    text-decoration: none;
}

.monthbar__step:hover {
    color: var(--accent-text);
}

.monthbar__step:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/*
 * The month as one row of marks.
 *
 * What the old grid was genuinely good at, kept and shrunk. It carries no
 * numbers and it is not a control: somebody reading their own consistency is
 * looking at the shape of the row, not counting dates. Thirty-one marks fit
 * on the narrowest phone because each one is a share of the row rather than a
 * fixed width.
 */
.daystrip__host {
    margin-block-end: 1rem;
}

.daystrip {
    display: flex;
    gap: 2px;
    align-items: stretch;
    block-size: 1.5rem;
}

.daystrip__day {
    flex: 1 1 0;
    min-inline-size: 0;
    border-radius: 2px;
    background: var(--body-base);
}

.daystrip__day--on {
    background: var(--accent);
}

.daystrip__caption {
    margin: var(--space-2) 0 0;
    font-size: 0.8125rem;
    color: var(--text-dim);
}

/*
 * The date on a session row.
 *
 * Tabular so a column of them lines up, and quiet: the row's subject is what
 * was trained, and the date is how somebody finds it again.
 */
.rowx__when {
    font-variant-numeric: tabular-nums;
    color: var(--text-dim);
    font-size: 0.875rem;
    white-space: nowrap;
}

/* --- tiles --- */

.tiles {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-3);
}

.tile {
    display: flex;
    flex-direction: column;
    min-inline-size: 0;
    padding: var(--space-4) var(--space-4) 1rem;
    border-radius: var(--radius-tile);
    background: var(--panel);
    box-shadow: var(--card-shadow-raised);
}

.tile--wide {
    grid-column: 1 / -1;
}

/*
 * The one tile on a screen that is the answer.
 *
 * A wash of the accent rather than a block of it: the figure on top has to
 * keep its contrast, and 3.6:1 does not survive being put on a saturated
 * ground. This is why there is a wash and not a fill.
 */
.tile--accent {
    background: linear-gradient(160deg, rgba(var(--accent-rgb), 0.16), rgba(var(--accent-rgb), 0.05));
}

.tile__label {
    margin: 0 0 var(--space-2);
    font-size: 0.8125rem;
    font-weight: 700;
    letter-spacing: 0;
    color: var(--text-dim);
}

.tile__lead {
    margin: 0 0 0.15rem;
    font-size: 0.8125rem;
    line-height: 1.35;
    color: var(--text-main);
}

.tile__figure {
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
    margin: 0.1rem 0 0;
    flex-wrap: wrap;
}

.tile__number {
    font-size: 1.875rem;
    font-weight: 700;
    letter-spacing: -0.03em;
    line-height: 1.05;
    font-variant-numeric: tabular-nums;
    color: var(--text-main);
}

.tile__unit {
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--text-dim);
}

.tile__against {
    margin: var(--space-2) 0 0;
    font-size: 0.6875rem;
    font-variant-numeric: tabular-nums;
    color: var(--text-dim);
}

.tile__graphic {
    margin-block-start: var(--space-3);
}

/*
 * Which way it went.
 *
 * The arrow carries the meaning and the tint is decoration on top of it: a
 * green dot and a red dot that differ only in hue say nothing to somebody who
 * cannot tell them apart.
 */
.trend {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 1.3rem;
    block-size: 1.3rem;
    border-radius: var(--radius-pill);
    font-size: 0.75rem;
    font-weight: 700;
    align-self: center;
}

.trend--up {
    background: rgba(22, 163, 74, 0.14);
    color: var(--success-color);
}

.trend--down {
    background: rgba(var(--accent-rgb), 0.14);
    color: var(--accent-text);
}

.trend--level {
    background: var(--canvas);
    color: var(--text-dim);
}

/* --- rows --- */

/*
 * A list of rows, as one object.
 *
 * The card belongs to the list, not to each row: the hairlines inside are what
 * separate them, and a border around every row would be a stack of boxes. This
 * is the shape every settings screen on every phone is made of, and the reason
 * those screens are scannable — one silhouette repeated, so the eye reads the
 * titles and stops only where it means to.
 */
.rowx-list {
    display: flex;
    flex-direction: column;
    background: var(--panel);
    border-radius: var(--radius-card);
    padding-inline: 1rem;
    margin-block: 0.75rem;
    box-shadow: var(--card-shadow-raised);
}

.rowx {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    inline-size: 100%;
    min-block-size: var(--touch-target);
    padding: var(--space-2) 0;
    border: 0;
    background: none;
    text-align: start;
    color: inherit;
    font: inherit;
    border-block-end: 1px solid var(--border);
}

.rowx:last-child {
    border-block-end: 0;
}

.rowx--tappable {
    cursor: pointer;
    text-decoration: none;
}

.rowx__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 2.1rem;
    block-size: 2.1rem;
    flex-shrink: 0;
    border-radius: var(--radius-control);
    background: rgba(var(--accent-rgb), 0.12);
    color: var(--accent-text);
}

.rowx__icon .icon {
    inline-size: 1.15rem;
    block-size: 1.15rem;
}

.rowx__text {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    flex: 1;
    min-inline-size: 0;
}

.rowx__title {
    font-size: 0.9375rem;
    font-weight: 550;
    color: var(--text-main);
}

.rowx__note {
    font-size: 0.75rem;
    line-height: 1.35;
    color: var(--text-dim);
}

/*
 * A diagnostic's value, on the end of its line.
 *
 * Monospace because these are read by comparison — a version against a
 * release, a size against last week — and a column of figures that lines up is
 * what makes that possible at a glance.
 */
.fact__value {
    font-family: var(--font-mono);
    font-size: 0.8125rem;
    color: var(--text-dim);
    /*
     * It wraps rather than running into the edge.
     *
     * A version is `v1.0.0-Beta.01-76-ga7154d9-dirty` — a git describe string,
     * the longest value on the screen and the one most likely to be read
     * character by character. Held on one line it finished flush against the
     * card's edge with nowhere to go; it is allowed to take a second line and
     * to end where the other values end.
     */
    text-align: end;
    overflow-wrap: anywhere;
}

/* The counts belong to the heading above them, not to the card below. */
.admin__counts {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    margin-block: 0 var(--space-2);
}

/* The sum, at the end of the line and in the text's own colour: it is the
   figure somebody looks for, and the three beside it are its parts. */
.admin__total {
    flex: none;
    color: var(--text-main);
    font-weight: 600;
}

/* A broken audit chain is the one thing on this screen worth a colour. */
.fact--bad .rowx__note {
    color: var(--danger-color);
}

/*
 * What a setting is set to, on the end of its own row.
 *
 * The value in the row's own text colour rather than the dim one used for
 * facts: this is the answer somebody came to the row for, and the row's title
 * is the question.
 */
.choice__value {
    color: var(--text-dim);
}

/* The mark on the chosen option. Where a chevron would be, because a row that
   opens nothing must not wear the arrow that says it does. */
.choice__tick {
    inline-size: 1.125rem;
    block-size: 1.125rem;
    flex-shrink: 0;
    fill: none;
    stroke: var(--accent-text);
    stroke-width: 2.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* An option row leads nowhere, so it has no chevron at all. */
.choice__option .rowx__chevron {
    display: none;
}

/*
 * Every option starts where its row starts, whatever script it is in.
 *
 * A language list is the one place this bites: العربية and فارسی are strong
 * right-to-left runs, and in a left-to-right interface they were laid out at
 * the far end of the line while the other nineteen sat at the near one — two
 * rows out of twenty-one looking like a mistake. Isolating each label makes it
 * one unit placed by the row's own direction, and `start` keeps that correct
 * when the whole interface is in Arabic and the near end is the right.
 */
.choice__option .rowx__title {
    unicode-bidi: isolate;
    text-align: start;
}

.choice__option--chosen .rowx__title {
    color: var(--accent-text);
}

.rowx__trailing {
    flex-shrink: 0;
    font-size: 0.875rem;
    font-variant-numeric: tabular-nums;
    color: var(--text-dim);
}

.rowx__chevron {
    inline-size: 1rem;
    block-size: 1rem;
    flex-shrink: 0;
    fill: none;
    stroke: var(--text-dim);
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    opacity: 0.55;
}

/* A row is not a link until it is one; a plain row has no chevron. */
.rowx:not(.rowx--tappable) .rowx__chevron {
    display: none;
}

/* --- a screen --- */

/*
 * A screen is a stack of cards on the canvas, not one card with headings.
 *
 * The title lives on the canvas above them rather than inside a box, which is
 * what every native application does and what makes the cards read as content
 * rather than as chapters of a document.
 */
.screen {
    display: flex;
    flex-direction: column;
    inline-size: 100%;
    min-inline-size: 0;
    /* The top air the scroll container gave up, so a sticky header can reach
       the real top edge. A screen that leads with a header takes it away
       again below. */
    padding-block-start: 1.5rem;
}

.screen__head {
    padding: 0.25rem var(--space-1) 1rem;
}

.screen__eyebrow {
    margin: 0 0 0.15rem;
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0;
    color: var(--text-dim);
}

.screen__title {
    margin: 0;
    font-size: 1.75rem;
    font-weight: 700;
    letter-spacing: -0.03em;
    line-height: 1.15;
    color: var(--text-main);
}

.screen__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    margin-block-start: var(--space-2);
}

/*
 * A day on the path.
 *
 * Filled with the panel colour and stroked with the line's own colour, so it
 * sits on the line as a joint rather than beside it as a mark. Non-scaling
 * strokes, because the chart's viewBox is stretched to the card's width and
 * anything else would come out oval.
 */
.band-chart__day {
    fill: var(--panel);
    stroke: var(--text-main);
    stroke-width: 1.4;
    vector-effect: non-scaling-stroke;
}

.today__count {
    margin: var(--space-2) 0 0;
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
    color: var(--text-dim);
}

/*
 * The day axis under the path.
 *
 * A grid with one cell per point rather than a flex row, so every label sits
 * exactly under the dot it names — with flex they drift as soon as some of
 * them are blank.
 */
.band-chart__days {
    display: grid;
    margin-block-start: var(--space-2);
    font-size: 0.625rem;
    font-variant-numeric: tabular-nums;
    color: var(--text-dim);
}

.band-chart__day-label {
    grid-row: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.05rem;
    line-height: 1.15;
    white-space: nowrap;
}

.band-chart__weekday {
    /* Lower case, because a calendar strip is not shouting and a three-letter
       weekday in capitals is wider than the column it has to fit in. */
    text-transform: lowercase;
    opacity: 0.75;
}

.band-chart__daynum {
    font-weight: 600;
}

/* Today, marked the same way the dot above it is: filled in rather than
   underlined. */
.band-chart__day-label--now {
    color: var(--accent-text);
}

.band-chart__day-label--now .band-chart__weekday {
    opacity: 1;
}

.path__body {
    margin: var(--space-2) 0 1rem;
    font-size: 0.875rem;
    line-height: 1.5;
    color: var(--text-dim);
}

.path__foot {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.75rem;
    margin-block-start: var(--space-3);
}

.path__foot .path__summary {
    margin: 0;
    flex: 1;
}

/*
 * The information control.
 *
 * It had no styling at all — a bare button rendering the letter i, which the
 * browser drew as a small grey pill and measured 19 by 21 pixels. That was
 * survivable while it explained one card. It is not survivable now: it is the
 * way to reach every explanation in the application, and 19 pixels is a target
 * a thumb misses.
 *
 * The visible circle stays small on purpose — it is punctuation beside a
 * heading, not a control competing with it. The target it answers to is
 * bigger than the circle, drawn by a pseudo-element that has no size of its
 * own on the page, so the hit area grows without the layout moving.
 */
.infodot {
    position: relative;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 1.5rem;
    block-size: 1.5rem;
    padding: 0;
    border: 1px solid var(--card-border);
    border-radius: 50%;
    background: var(--surface-solid);
    color: var(--text-dim);
    font-family: var(--font-sans);
    font-size: 0.8125rem;
    font-style: italic;
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
    transition:
        color 0.15s var(--ease-standard),
        border-color 0.15s var(--ease-standard);
}

.infodot::after {
    content: "";
    position: absolute;
    /* Takes the 24px circle out to a 48px target, which is what the plan asks
       of every control and what a thumb actually needs. */
    inset: -0.9rem;
}

.infodot:hover,
.infodot--open {
    border-color: var(--accent);
    color: var(--accent-text);
}

.infodot:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/*
 * The explanation, as a popover over everything.
 *
 * It expanded a paragraph in the flow before, which pushed the rest of the
 * screen down — a disclosure rather than an answer, and it read as a banner
 * rather than as a reply to the question somebody had just asked.
 *
 * `position: fixed` and no `inset`: the popover is placed from script against
 * the dot that opened it, and it lives in the top layer, so it is never
 * clipped by the sheet or card around it. The width is capped so a long
 * explanation does not become a single line across a desktop.
 */
/* An explanation, as the only thing its sheet holds. */
.sheet__prose {
    margin: 0;
    font-size: 0.9375rem;
    line-height: 1.55;
    color: var(--text-dim);
}

.infodot__wrap {
    display: inline-flex;
    flex-shrink: 0;
}

.infodot__host {
    position: relative;
    flex-shrink: 0;
}

.infodot__host .cardx__why {
    margin: 0.5rem 0 0;
}

/*
 * A food picture.
 *
 * An emoji rendered large rather than an icon: the platform's own artwork is
 * better than anything a few hand-drawn paths can be, and it is already on the
 * device. The font stack names the emoji fonts first so it does not land in
 * the interface face, which on some systems has its own flat monochrome
 * versions of these characters.
 */
.food-icon {
    flex-shrink: 0;
    font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji",
        "Twemoji Mozilla", sans-serif;
    font-size: 1.5rem;
    line-height: 1;
    /* Emoji sit on the text baseline and are taller than the line around
       them; this keeps a row of them level with the words beside them. */
    display: inline-block;
    transform: translateY(0.05em);
}

/*
 * A note on capitals.
 *
 * Lastboard sets small labels in upper case with letter-spacing, and LastGYM
 * inherited that. It is removed here on purpose, and it is the seventh place
 * this project diverges from what it inherited.
 *
 * The reason is specific rather than taste: a phone screen is mostly labels —
 * every measurement, every field, every section — and a screen of upper-case
 * micro-labels reads as a control panel. It is also measurably worse in the
 * languages this application ships in: German compounds and Greek go wider in
 * capitals and start wrapping in a column that fits perfectly in sentence
 * case, and Japanese, Korean and Chinese have no capitals at all, so the rule
 * silently applies to five of the twenty and not the rest.
 */

/* ==========================================================================
   The screen frame.

   A header that sticks and blurs, a large title for a root screen, bare
   measurement grids, section heads that carry the prose, selection pills and a
   glowing hero figure. Everything a phone application has above and around its
   content, and none of which this application had.
   ========================================================================== */

/*
 * The header of a screen you navigated into.
 *
 * Sticky and translucent: the content passes blurred underneath instead of
 * stopping at an opaque bar. Where backdrop-filter is unsupported the fallback
 * background is already opaque enough to read against, so nothing is lost but
 * the effect.
 */
.topbar {
    position: sticky;
    inset-block-start: 0;
    z-index: 20;
    display: grid;
    grid-template-columns: var(--touch-target) 1fr var(--touch-target);
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    /*
     * Out to both edges of the window, and back again as padding.
     *
     * The two numbers were written independently — -1rem against a 1.25rem
     * gutter — so four pixels down each side stayed uncovered and the content
     * scrolled past the bar in the clear. One token feeds both now, which is
     * the only way they cannot drift apart again.
     */
    margin-inline: calc(-1 * var(--gutter));
    padding-inline: var(--gutter);
    background: color-mix(in srgb, var(--canvas) 78%, transparent);
    backdrop-filter: blur(18px) saturate(150%);
    -webkit-backdrop-filter: blur(18px) saturate(150%);
}

@supports not (backdrop-filter: blur(1px)) {
    .topbar {
        background: var(--canvas);
    }
}

/*
 * The round buttons.
 *
 * A circle rather than a bare glyph, because a bare chevron in the corner is a
 * link on a page; a filled circle is a control, and it is the full 48px a
 * thumb needs without looking like a button bar.
 */
.topbar__round {
    inline-size: var(--touch-target);
    block-size: var(--touch-target);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-pill);
    border: 1px solid rgba(var(--tone-rgb), 0.18);
    background: color-mix(in srgb, var(--panel) 70%, transparent);
    color: var(--text-main);
    text-decoration: none;
    cursor: pointer;
    transition: transform 0.12s var(--ease-standard), background 0.15s var(--ease-standard);
}

.topbar__round:active {
    transform: scale(0.94);
}

.topbar__round--empty {
    border: none;
    background: none;
    cursor: default;
}

.topbar__glyph {
    inline-size: 22px;
    block-size: 22px;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.topbar__text {
    min-inline-size: 0;
    text-align: center;
}

.topbar__title {
    margin: 0;
    font-size: 1.0625rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--text-main);
    /* One line. A screen whose name does not fit is a screen with too long a
       name, and a header that grows a second line moves everything below it. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.topbar__subtitle {
    margin: 0.05rem 0 0;
    font-size: 0.8125rem;
    color: var(--text-dim);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The header of a root screen: no way back, so the name goes large and left. */
.bigtitle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    /*
     * A margin below rather than padding, so it collapses.
     *
     * As padding it was space inside the title's own box, and whatever came
     * next added its margin on top of it: a screen whose first block began
     * with a section heading got 16 plus 24, and one that began with a card
     * got 16. Same title, two different distances, and neither chosen. As a
     * margin the larger of the two wins and the answer is the same every time.
     */
    padding-block: var(--space-3) 0;
    margin-block-end: var(--space-6);
}

.bigtitle__text {
    margin: 0;
    font-size: 1.75rem;
    font-weight: 800;
    letter-spacing: -0.03em;
    color: var(--text-main);
}

.bigtitle__trailing {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

/*
 * A measurement, with nothing around it.
 *
 * The most useful thing the study turned up: sibling numbers do not each want
 * a box. Four figures in two columns with a dim label over each read at a
 * glance; the same four in four bordered cards read as a form to fill in.
 */
/*
 * A grid of measurements, aligned across the row.
 *
 * The rows are a subgrid, so the label, the figure and the status line of the
 * two cells beside each other sit on the same three lines whatever length the
 * labels are. Without it the first translation that needed two lines for a
 * label — Indonesian's "Pinggang terhadap tinggi" — pushed its own figure down
 * and left the pair reading as a staircase.
 *
 * That is not an edge case in an application shipping twenty languages: label
 * lengths differ by half again between English and German, and designing to
 * the English ones means every other language looks broken.
 */
.stats {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1.25rem 1rem;
    margin-block: 1rem;
}

.stat {
    display: grid;
    grid-row: span 3;
    grid-template-rows: subgrid;
    /* The label takes the first row, the figure the second, the status the
       third — and a stat with no status simply leaves its third row empty
       rather than pulling the figure down out of line. */
    align-content: start;
}

.stat--wide {
    grid-column: 1 / -1;
}

/*
 * Where subgrid is not available the rows fall back to reserving two lines for
 * a label, which aligns the common case and degrades to the old behaviour
 * rather than to a broken one.
 */
@supports not (grid-template-rows: subgrid) {
    .stat {
        display: block;
    }

    .stat__label {
        min-block-size: 2.5em;
    }
}

.stat__label {
    margin: 0 0 0.15rem;
    font-size: 0.875rem;
    color: var(--text-dim);
}

/*
 * A number and its unit on one line.
 *
 * The unit is small, still bold and sits tight against the value. A unit at
 * the same size competes with the number it belongs to, and the eye then has
 * to read both to find the one that matters.
 */
.figure {
    margin: 0;
    display: flex;
    align-items: baseline;
    gap: 0.15rem;
    color: var(--text-main);
}

.figure__value {
    font-size: 1.75rem;
    font-weight: 700;
    letter-spacing: -0.03em;
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
}

.figure__unit {
    font-size: 0.8125rem;
    font-weight: 700;
    color: var(--text-dim);
}

/* How a number is doing: a mark, then the word. Never the colour alone. */
.statusline {
    margin: 0.2rem 0 0;
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: 0.8125rem;
}

.statusline__mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 1rem;
    block-size: 1rem;
    border-radius: var(--radius-pill);
    font-size: 0.6875rem;
    font-weight: 700;
    color: var(--panel);
}

.statusline--good {
    color: var(--success-color);
}
.statusline--good .statusline__mark {
    background: var(--success-color);
}

.statusline--warn {
    color: var(--warning-color);
}
.statusline--warn .statusline__mark {
    background: var(--warning-color);
}

.statusline--flat {
    color: var(--text-dim);
}
.statusline--flat .statusline__mark {
    background: var(--text-dim);
}

/*
 * The head of a section.
 *
 * Where the writing goes. This project explains itself — what a method is, why
 * a number is missing, what a setting costs — and that prose has to live
 * somewhere that is neither repeated inside every card nor thrown away. Once,
 * dim, at the top of the section it describes.
 */
/* The same rhythm as .section__title, because it is the same thing. */
.sectionhead {
    margin-block: var(--space-6) var(--space-3);
}

.sectionhead__top {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
}

/*
 * The dot sits against its heading, not against the far edge.
 *
 * The row spreads its ends apart so the action lands on the right; without
 * this the dot goes with it and ends up explaining whatever is over there.
 */
.sectionhead__top .infodot {
    margin-inline-end: auto;
}

.sectionhead__title {
    margin: 0;
    font-size: 1.125rem;
    font-weight: 700;
    letter-spacing: -0.015em;
    color: var(--text-main);
}

.sectionhead__action {
    display: inline-flex;
    align-items: center;
    gap: 0.1rem;
    border: none;
    background: none;
    padding: 0.25rem 0;
    font: inherit;
    font-size: 0.9375rem;
    font-weight: 600;
    color: var(--accent-text);
    text-decoration: none;
    cursor: pointer;
}

.sectionhead__action .rowx__chevron {
    inline-size: 16px;
    block-size: 16px;
}

.sectionhead__note {
    margin: var(--space-2) 0 0;
    font-size: 0.9375rem;
    line-height: 1.5;
    color: var(--text-dim);
}

/*
 * Selection pills.
 *
 * What a phone uses where a desktop uses a row of buttons or a dropdown: every
 * option visible at once, the chosen one filled. Scrolls sideways on its own
 * so a long set never makes the page scroll.
 */
.chips {
    display: flex;
    gap: 0.5rem;
    overflow-x: auto;
    scrollbar-width: none;
    padding-block: 0.25rem;
    /* Scrolls sideways out to the edges of the window, through the same
       gutter as the header. */
    margin-inline: calc(-1 * var(--gutter));
    padding-inline: var(--gutter);
}

.chips::-webkit-scrollbar {
    display: none;
}

/*
 * The two ways to add a cardio session, side by side.
 *
 * Two buttons rather than one primary and a link: recording by hand and
 * uploading a watch file are the same act by different means, and neither is
 * the lesser one. They wrap on the narrowest phone rather than shrinking, so
 * neither ends up with its label cut.
 */
.cardio-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-block-end: var(--space-4);
}

.cardio-actions > .button {
    flex: 1 1 auto;
    justify-content: center;
}

/* ==========================================================================
   The mascot.

   A kettlebell with ten faces. It is read before the sentence beside it, which
   is the whole reason it exists: somebody glancing at the home screen knows how
   their week went before finishing the first word.
   ========================================================================== */

.mascot {
    inline-size: 4rem;
    block-size: 4rem;
    flex-shrink: 0;
    display: block;
}

/*
 * The body is the accent, and the face is punched out of it in the panel
 * colour rather than drawn in a third colour. Two colours is what makes this
 * survive both themes without a second set of shapes.
 */
.mascot__body {
    fill: var(--accent);
}

.mascot__handle {
    fill: none;
    stroke: var(--accent);
    stroke-width: 9;
    stroke-linecap: round;
}

/*
 * The arms, thinner than the handle.
 *
 * At the same weight they read as a second handle sticking out of the sides.
 * Thinner and round-capped they read as limbs, which is the difference between
 * a decorated object and somebody.
 */
.mascot__arm {
    fill: none;
    stroke: var(--accent);
    stroke-width: 7;
    stroke-linecap: round;
}

.mascot__ink {
    fill: var(--panel);
}

.mascot__line {
    fill: none;
    stroke: var(--panel);
    stroke-width: 4;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.mascot__line--thin {
    stroke: var(--accent);
    stroke-width: 3;
}

.mascot__accent {
    fill: var(--accent);
}

/*
 * An empty screen with somebody in it.
 *
 * Centred and given room, unlike the mascot on the path card which stands
 * beside a sentence: here it is the only thing there, and a character alone
 * in the middle of a page is the difference between "no data" and being told
 * there is nothing yet.
 */
.nothing {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.75rem;
    padding-block: 2rem 1rem;
    text-align: center;
}

.nothing .mascot {
    inline-size: 5.5rem;
    block-size: 5.5rem;
}

.nothing__text {
    margin: 0;
    max-inline-size: 22rem;
    color: var(--text-dim);
    line-height: 1.55;
}

/* Beside the sentence it belongs to, not above it. */
.mascot-row {
    display: flex;
    align-items: center;
    gap: var(--space-4);
}

.mascot-row__text {
    min-inline-size: 0;
}

/*
 * The connection light.
 *
 * A state, not a control, and that distinction decided the shape. A switch
 * says you may flip it, and nobody can decide to be online; a dot and a word
 * say what is true and invite nothing. It replaced four paragraphs that took
 * turns at the top of the home screen, one of which somebody could meet for
 * weeks at a time.
 */
.conn {
    flex-shrink: 0;
}

/*
 * The greeting on one side, the connection on the other.
 *
 * `align-items: start` rather than centre: the greeting is two lines and the
 * light is one, and centring would float the light halfway down the name.
 */
.screen__head--withstatus {
    display: flex;
    align-items: start;
    justify-content: space-between;
    gap: 1rem;
}

/*
 * The explanation is a block under the header, in the flow.
 *
 * It was pulled out of the corner with `position: absolute` so it could use
 * the full width, which took it out of the flow and dropped it on top of the
 * greeting. Sitting under the header as an ordinary block, it pushes what
 * follows down, which is what an expanding disclosure has to do.
 */
.conn__why {
    margin-block-end: 0.75rem;
}

.conn__why .cardx__why {
    margin-block-start: 0;
}

.conn__line {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.conn__dot {
    inline-size: 0.6rem;
    block-size: 0.6rem;
    border-radius: 50%;
    flex-shrink: 0;
    background: var(--text-dim);
}

/*
 * Green when connected, and not the house accent.
 *
 * The accent means "this is the thing to touch" everywhere else in this
 * application, and a permanent orange dot at the top of the home screen would
 * be the loudest thing on it saying the quietest thing there is.
 */
.conn__dot--ok {
    background: var(--success-color);
}

.conn__dot--away {
    background: var(--text-dim);
}

.conn__dot--bad {
    background: var(--danger-color);
}

.conn__word {
    font-size: 0.875rem;
    color: var(--text-dim);
}

/*
 * A switch, and the row it lives in.
 *
 * The whole row is not the target — the switch is — because a row that toggles
 * when you touch its explanation is a row that fires while somebody is reading
 * it. What the row gives is the sentence: a switch alone says "on" without
 * saying what is on.
 */
/*
 * Switches live in a card, and a run of them is one card.
 *
 * They were loose on the canvas — five of them on the privacy screen, each a
 * line of text with a switch at the end and nothing tying them together. That
 * is a web form. Every phone groups settings rows on a panel with hairlines
 * between them, and the grouping is not decoration: it says these five belong
 * to one question, and it gives the row an edge so the switch at the end reads
 * as part of the line rather than as something floating beside it.
 *
 * The run is found by adjacency rather than by a wrapper, so no call site has
 * to remember to add one: the first row of a run rounds its top, the last
 * rounds its bottom, and a lone switch rounds both.
 */
.switchrow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding-block: var(--space-3);
    padding-inline: var(--space-4);
    background: var(--panel);
    box-shadow: var(--card-shadow-raised);
}

.switchrow + .switchrow {
    border-block-start: 1px solid var(--border);
}

.switchrow:not(.switchrow + .switchrow) {
    margin-block-start: var(--space-3);
    border-start-start-radius: var(--radius-card);
    border-start-end-radius: var(--radius-card);
}

.switchrow:not(:has(+ .switchrow)) {
    margin-block-end: var(--space-3);
    border-end-start-radius: var(--radius-card);
    border-end-end-radius: var(--radius-card);
}

.switchrow__text {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    min-inline-size: 0;
}

.switchrow__title {
    font-size: 0.9375rem;
}

.switchrow__note {
    font-size: 0.8125rem;
    color: var(--text-dim);
}

.switch {
    flex: 0 0 auto;
    position: relative;
    inline-size: 3.25rem;
    block-size: 1.9rem;
    border: none;
    border-radius: 1rem;
    background: var(--body-base);
    cursor: pointer;
    padding: 0;
    transition: background-color 0.2s var(--ease-standard);
}

/* The row is the control now, so it takes the pointer and the height. */
.switchrow {
    cursor: pointer;
    min-block-size: var(--touch-min);
}

.switch__knob {
    position: absolute;
    inset-block-start: 0.2rem;
    /* Logical, so the knob starts on the correct side in Arabic. */
    inset-inline-start: 0.2rem;
    inline-size: 1.5rem;
    block-size: 1.5rem;
    border-radius: 50%;
    background: var(--panel);
    box-shadow: var(--shadow-sm);
    transition: inset-inline-start 0.2s var(--ease-standard);
}

.switch--on {
    background: var(--accent);
}

.switch--on .switch__knob {
    inset-inline-start: 1.55rem;
}

.switch:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/*
 * The form variant: every option visible, on as many lines as it takes.
 *
 * The bleed and the sideways scroll above are for a filter bar at the top of a
 * screen. Inside a sheet they push the last option past the edge, which is the
 * dropdown's own failing wearing different clothes.
 */
/*
 * Two or three exclusive choices are a segmented control.
 *
 * Not a decoration: a row of pills that stops a third of the way across reads
 * as a list somebody left unfinished, and it is the single clearest tell of a
 * web layout on a phone. Filling the width also makes every option the same
 * size, which is what says they are alternatives rather than a sequence.
 */
.chips--split {
    overflow-x: visible;
    margin-inline: 0;
    padding-inline: 0;
}

.chips--split > .chip {
    flex: 1 1 0;
    min-inline-size: 0;
    justify-content: center;
    text-align: center;
}

/*
 * Fills the line, sharing the slack rather than the width.
 *
 * `flex: 1 1 auto` gives every option its own content width plus an equal part
 * of whatever is left, so the row is full and nothing is squeezed. The
 * two-way choices above use `1 1 0` instead — equal halves, which reads as
 * "these are alternatives" — and that only works while the labels are of
 * similar length. Three roles are not.
 */
.chips--fill > .chip {
    flex: 1 1 auto;
    justify-content: center;
    text-align: center;
    /*
     * Tighter than a pill standing on its own, which is what gets three
     * options onto one line.
     *
     * A chip filtering a list is a thing you point at among others and its
     * generous padding is what makes it look tappable. These are one control
     * split into parts, and the part that matters is the word: "Member",
     * "Coach" and "Administrator" at sixteen pixels a side add up to more than
     * a phone is wide and drop the longest onto a row of its own. At eight
     * they fit, and the target does not shrink — each still grows to fill its
     * share of the line, and the height is untouched.
     */
    padding-inline: var(--space-2);
}

/*
 * The accents, as colour rather than as a list of words.
 *
 * A row of names would ask somebody to imagine what "teal" looks like in an
 * application they are looking at. Each swatch is the colour it sets, and the
 * name is on it for a screen reader and for anybody who cannot tell two of
 * them apart. 44 across, because a colour is still a control.
 */
/*
 * The swatches, spread across the width rather than queued at the left.
 *
 * Six fitted a row and the seventh — the picker — wrapped underneath on its
 * own, which reads as something that did not fit rather than as a choice. A
 * grid of equal columns puts all seven on one line at any phone width and
 * spaces them by the room available, and when there genuinely is not enough
 * room it wraps into even rows instead of leaving one orphan on the left.
 */
.accents {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(var(--touch-min), 1fr));
    justify-items: center;
    /*
     * The tightest step on the scale, because the gap here is between cells
     * and not between the circles. Each 44px target holds a 28px disc, so a
     * 4px cell gap still leaves about eighteen between two colours — and at
     * 8px only six columns fitted a 390px phone, which is what left the
     * picker stranded on a line of its own.
     */
    gap: var(--space-1);
}

.accents__one {
    inline-size: var(--touch-min);
    block-size: var(--touch-min);
    padding: 0;
    border: 0;
    border-radius: var(--radius-pill);
    cursor: pointer;
    /* The swatch is drawn inside, so the ring the chosen one wears has
       somewhere to go without changing the target's size. */
    background: none;
    display: flex;
    align-items: center;
    justify-content: center;
}

.accents__one::before {
    content: "";
    inline-size: 28px;
    block-size: 28px;
    border-radius: var(--radius-pill);
    background: var(--swatch);
}

/* Chosen: a ring in the colour itself, with a gap so the swatch stays a
   circle rather than becoming a target with a border. */
.accents__one--chosen::before {
    box-shadow: 0 0 0 3px var(--canvas), 0 0 0 6px var(--swatch);
}

.accents__one:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

.accents__one[data-accent-swatch="orange"] { --swatch: #e8520e; }
.accents__one[data-accent-swatch="blue"]   { --swatch: #2563eb; }
.accents__one[data-accent-swatch="green"]  { --swatch: #16a34a; }
.accents__one[data-accent-swatch="purple"] { --swatch: #7c3aed; }
.accents__one[data-accent-swatch="pink"]   { --swatch: #db2777; }
.accents__one[data-accent-swatch="teal"]   { --swatch: #0d9488; }

/*
 * The seventh swatch: the phone's own colour picker.
 *
 * It is a real `<input type="color">`, because on iOS and Android that is the
 * system picker — the grid, the spectrum and the sliders somebody already
 * knows. What has to be undone is the chrome browsers wrap it in: a bevelled
 * box with a swatch inside and a border, which next to six flat circles looks
 * like a control from a different application.
 *
 * The wheel behind it is a plain conic gradient, so the swatch reads as "any
 * colour" rather than as whichever one happens to be selected. Once a colour
 * has been picked, the chosen ring goes around it in that colour.
 */
.accents__pick {
    position: relative;
    cursor: pointer;
    --swatch: conic-gradient(
        from 0deg,
        #e8520e, #d4a017, #16a34a, #0d9488, #2563eb, #7c3aed, #db2777, #e8520e
    );
}

/*
 * The input covers the swatch and shows none of itself.
 *
 * It cannot be the swatch: a colour input is a replaced element that draws
 * what the browser decides, with no ::before to put the wheel in. So the label
 * draws the circle and this lies over it, transparent and the full size of the
 * target, which keeps the tap opening the system picker and keeps the control
 * reachable by keyboard — `opacity: 0` leaves it focusable where
 * `visibility: hidden` would not.
 */
.accents__input {
    position: absolute;
    inset: 0;
    inline-size: 100%;
    block-size: 100%;
    padding: 0;
    border: 0;
    opacity: 0;
    cursor: pointer;
}

.accents__pick:focus-within::before {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.chips--wrap {
    flex-wrap: wrap;
    overflow-x: visible;
    margin-inline: 0;
    padding-inline: 0;
}

.chip {
    flex-shrink: 0;
    border: none;
    border-radius: var(--radius-pill);
    padding: var(--space-2) var(--space-4);
    /* Measured at 39px, which is under both platforms' floor. These are
       choices somebody makes with a finger, not decoration. */
    min-block-size: var(--touch-min);
    display: inline-flex;
    align-items: center;
    font: inherit;
    font-size: 0.9375rem;
    font-weight: 600;
    color: var(--text-main);
    background: color-mix(in srgb, var(--text-dim) 14%, transparent);
    cursor: pointer;
    transition: transform 0.12s var(--ease-standard);
}

.chip:active {
    transform: scale(0.96);
}

.chip--on {
    background: var(--tone);
    color: #ffffff;
}

/*
 * The one number a screen is about.
 *
 * Their screens open with something enormous and then get down to detail. We
 * have no illustrator, so a soft radial wash in the screen's tone does the
 * work: same effect at the top of a screen, no asset and no licence.
 */
.hero {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.15rem;
    padding-block: 2rem 1.5rem;
    text-align: center;
}

.hero__glow {
    position: absolute;
    inset-block-start: 0;
    inline-size: min(80%, 17rem);
    aspect-ratio: 1;
    border-radius: var(--radius-pill);
    background: radial-gradient(circle, rgba(var(--tone-rgb), 0.30), rgba(var(--tone-rgb), 0) 68%);
    pointer-events: none;
}

.hero__glyph {
    position: relative;
    margin: 0 0 0.25rem;
    font-size: 2rem;
    line-height: 1;
}

.hero .figure {
    position: relative;
    justify-content: center;
}

.hero .figure__value {
    font-size: 3rem;
}

.hero .figure__unit {
    font-size: 1rem;
}

.hero__label {
    position: relative;
    margin: 0;
    font-size: 0.9375rem;
    color: var(--text-dim);
}

.hero .statusline {
    position: relative;
    justify-content: center;
}

/*
 * A screen.
 *
 * No panel, no border, no shadow around the whole thing. Every screen used to
 * be one bordered white slab with its heading inside it, which is the single
 * detail that made this read as a web page rather than an application: a box
 * around an entire screen is a document, and grouping that groups everything
 * groups nothing.
 *
 * Cards still exist — around things that actually belong together.
 */
.screen {
    display: block;
}

/*
 * A button that stands alone on a screen goes full width.
 *
 * It used to sit inside a panel where a content-width pill had a visible box
 * around it. On the bare canvas the same pill floats in the middle of nothing,
 * looking like a mistake, and a long label in German or Spanish overflows it.
 * Only direct children: a button inside a card or a row is positioned by
 * whatever it is inside.
 */
.screen > .button,
.screen > div > .button {
    display: flex;
    inline-size: 100%;
    margin-block: 0.5rem;
}

/*
 * ...but never inside something that positions its own controls.
 *
 * A form row puts its button beside the field, a card decides its own layout,
 * and a row of chips is a row. Only a button standing on the screen with
 * nothing arranging it is the one that has to fill the width.
 */
/*
 * A row of actions holding exactly one action.
 *
 * .row exists to put two or three buttons side by side, and those stay side by
 * side. One on its own is not a row — it is a button standing on the screen,
 * and on a phone that means full width. :only-child says precisely that,
 * without a second class anybody has to remember to add.
 */
.screen .row > .button:only-child {
    flex: 1;
}

.screen .form .button,
.screen .cardx .button,
.screen .chips .button,
.screen .rowx-list .button {
    display: inline-flex;
    inline-size: auto;
    margin-block: 0;
}

.screen__intro {
    margin: 0 0 1rem;
    color: var(--text-dim);
    line-height: 1.55;
}

/*
 * The content starts right under an inner screen's header.
 *
 * Only the first block, which is what this was always for: the header already
 * carries the name, so nothing needs a title's worth of space above it. As
 * `> *` it applied to every child and overrode the section rhythm — a heading
 * that asks for 24 above got 12 on every inner screen and 24 everywhere else,
 * which is the same relationship arriving at two different numbers depending
 * on which screen you were looking at.
 */
.screen--inner > .topbar + * {
    margin-block-start: var(--space-3);
}

/*
 * A screen that opens with its header starts at the top edge.
 *
 * The header brings its own 0.5rem of padding and is the thing meant to touch
 * the top of the window: an inset above it is the seam this whole change is
 * about, and it would come back here if the screen kept the padding.
 */
.screen--inner {
    padding-block-start: 0;
}



/*
 * Choosing a file.
 *
 * A bare file input is the single most desktop-looking control there is: a
 * grey system button with "no file selected" beside it, sized by the browser,
 * unstyleable in the middle. The input is still the input — it has to be, that
 * is what opens the picker and what a screen reader announces — but its button
 * half is restyled into the same pill as everything else and the filename
 * moves onto its own line underneath.
 */
/*
 * A password field and the eye that reveals it.
 *
 * The eye sits inside the field rather than beside it: beside it, it reads as
 * a second control with its own meaning, and on a narrow phone it steals the
 * width the field needs. Inside, it is part of the box — which is what it is.
 *
 * The input keeps room for it on its own trailing edge, so a long password
 * scrolls under the eye instead of behind it.
 */
/*
 * The eye lives inside the box, and the box has to fill the wrapper for that
 * to be true.
 *
 * `.input` has no width of its own — it takes the browser's default size —
 * so an absolutely positioned button pinned to the wrapper's trailing edge
 * landed past the field's own border: measured at login, the field ended at
 * 338 and the eye ran from 301 to 345. It looked right in a screenshot and was
 * wrong by seven pixels, which is what comes of checking a control against the
 * end of its text rather than against its own edge.
 */
.reveal__wrap {
    position: relative;
    display: block;
}

.reveal__wrap .input {
    inline-size: 100%;
}

.input--password {
    padding-inline-end: calc(var(--touch-min) + var(--space-1));
}

.reveal {
    position: absolute;
    inset-inline-end: 0;
    inset-block: 0;
    inline-size: var(--touch-min);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 0;
    background: none;
    color: var(--text-dim);
    cursor: pointer;
}

.reveal__icon {
    inline-size: 20px;
    block-size: 20px;
}

/* Shown is a state worth seeing: the eye takes the accent while the password
   is in the open, so nobody leaves it that way without noticing. */
.reveal[aria-pressed="true"] {
    color: var(--accent-text);
}

.reveal:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -4px;
    border-radius: var(--radius);
}

.input[type="file"] {
    padding: 0.5rem;
    font-size: 0.875rem;
}

.input[type="file"]::file-selector-button {
    min-block-size: var(--touch-min);
    margin-inline-end: 0.75rem;
    padding-inline: var(--space-4);
    border: 0;
    border-radius: var(--radius-pill);
    background: var(--accent);
    color: #ffffff;
    font-family: inherit;
    font-size: 0.9375rem;
    font-weight: 600;
    cursor: pointer;
}

/*
 * A measurement whose value is an identifier.
 *
 * A version string is not a quantity: set at figure size it wraps to four
 * lines and pulls the grid apart, and nobody reads it the way they read a
 * weight. The shape stays, the size drops, and it breaks between its own parts
 * instead of mid-token.
 */
.stat--text .figure__value {
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: 0;
    overflow-wrap: anywhere;
}


/*
 * What an athlete granted, under their name on the coach's screen.
 *
 * It used .meta, which draws a rule above itself because it is the footer of a
 * card — inside a list item that put a hairline between a name and the list of
 * scopes belonging to it.
 */
.athlete__scopes {
    margin: 0.25rem 0 0;
    font-size: 0.8125rem;
    color: var(--text-dim);
}

/*
 * A list of rows nested inside a card that already exists.
 *
 * The coach's screen puts one of these inside each athlete's row — the scope
 * rows, and then whichever list of the athlete's own they opened — so a second
 * panel and a second shadow is a card inside a card. Only the rows are wanted
 * here, not the container.
 */
/*
 * A list with no base rule is a list with the browser's bullets.
 *
 * `.grant-list` had only the nested override below — no rule of its own — so
 * every top-level use of it rendered as a bulleted document list. It was the
 * whole of the routines screen until this week and it is still the
 * notification channels: literal disc markers, in an application whose every
 * other list is a card of rows. Nothing looked wrong in the stylesheet,
 * because the rule that would have been wrong was not there.
 */
.grant-list {
    margin: 0;
    padding: 0;
    list-style: none;
}

.grant-list .rowx-list,
.athlete-list .rowx-list,
.athlete-list .session-list,
.athlete-list .plain-list,
.athlete-list .grant-list {
    background: none;
    box-shadow: none;
    padding-inline: 0;
    margin-block: var(--space-2) 0;
}

/*
 * Charts in a right-to-left interface.
 *
 * The bug this fixes was invisible until somebody actually set dir="rtl": the
 * date labels under a chart are a CSS grid, so they mirrored, and the drawing
 * above them is an SVG with absolute coordinates, so it did not. The result
 * was a chart whose axis no longer described its own data — today's label at
 * one end and today's filled dot at the other. Nothing about it looked broken,
 * which is what made it worth finding before translating a word of Arabic.
 *
 * Mirroring the drawing rather than pinning the labels back, because that is
 * what the platforms do: in an Arabic interface a time axis runs right to
 * left, the same way the reading does. The transform costs nothing and there
 * is no text inside these drawings to come out backwards.
 */
[dir="rtl"] .plot,
[dir="rtl"] .band-chart {
    transform: scaleX(-1);
}

/*
 * A measurement stays in its own reading order.
 *
 * "66.3 t" is a number followed by a Latin unit, and in an RTL paragraph the
 * two are separate runs that the bidi algorithm is free to reorder. Isolating
 * them keeps the pair together and lets the surrounding direction place the
 * whole thing, which is the behaviour every locale wants: the value reads as
 * one token wherever it lands.
 */
.figure,
.chart__scale span,
.chart__tick,
.band-chart__day-label {
    unicode-bidi: isolate;
}

/*
 * Anything a person typed decides its own direction.
 *
 * The interface direction comes from the interface language, and what somebody
 * writes into it does not have to match: an Arabic interface gets an exercise
 * called "Back squat", a Spanish one gets a note written in Arabic, and a
 * bilingual gym gets both in the same list. Without this the neutral
 * characters — the full stop, the brackets, the digits — are placed by the
 * paragraph's direction rather than the sentence's, so a Latin note in an
 * Arabic interface comes out with its full stop at the front.
 *
 * `plaintext` reads the first strong character of each paragraph and lays that
 * paragraph out accordingly, which is exactly the rule a human would apply.
 */
.input,
textarea.input,
.rowx__title,
.grant__who,
.athlete__who,
.grant__scope {
    unicode-bidi: plaintext;
}

/*
 * A token secret, shown once.
 *
 * Wrapped anywhere rather than scrolled sideways: this is the one string in
 * the application somebody has to select all of, and a box they have to drag
 * inside is how half of it gets missed. Monospace because the alphabet
 * deliberately has no I, L, O or U and the reader still has to trust what they
 * are looking at.
 */
.tokens__secret {
    display: block;
    margin-block-start: 0.5rem;
    padding: var(--space-2) 0.75rem;
    border-radius: var(--radius-control);
    background: color-mix(in srgb, var(--text-dim) 12%, transparent);
    font-family: var(--font-mono);
    font-size: 0.875rem;
    overflow-wrap: anywhere;
    user-select: all;
}

.tokens__once {
    margin: 0;
    font-weight: 600;
}

/* A notice that is telling somebody something good happened. */
.notice--good {
    border-inline-start: 3px solid var(--success-color);
}

/*
 * A notice about something that needs attention but has lost nothing.
 *
 * Between the plain notice and the error: a synchronisation that finished and
 * left work behind is not a failure — the work is safe on the device — but it
 * is not the ordinary state either, and colouring it the same as "three sets
 * not sent yet" would hide it.
 */
.notice--warn {
    border-inline-start-color: var(--warning-color);
}

/* ==========================================================================
   The bottom sheet.

   It sits on the bottom edge, full width, with the corners rounded only at the
   top — the shape a phone uses for "this came from down there and goes back
   down there". Not centred and not a box in the middle of the screen, which is
   a desktop dialog and reads as an interruption rather than a drawer.
   ========================================================================== */

.sheet {
    /* margin-block-start: auto pushes it to the bottom; the inset-inline pair
       makes it full width. A <dialog> centres itself by default, and that
       default is exactly the desktop shape this is not. */
    margin: auto auto 0;
    /* Border-box, because the padding would otherwise be added to the hundred
       per cent: measured at 422px inside a 390px viewport, which is a sheet
       hanging off both edges of the phone it is meant to sit on. A <dialog> is
       in the top layer, so nothing clips it and nothing complains — the page
       does not even report an overflow. */
    box-sizing: border-box;
    inline-size: 100%;
    max-inline-size: 34rem;
    max-block-size: 88vh;
    /*
     * Room at the foot, which a sheet needs more than a page does.
     *
     * Sixteen pixels put the last control against the bottom edge of the
     * screen — and the bottom edge of a phone is where the gesture bar lives,
     * so a button sitting on it is a button competing with the system. Native
     * sheets leave a clear band there. The safe-area inset is added rather
     * than substituted: on a phone with a notch it is extra, and on one
     * without it is zero and this is the whole of it.
     */
    padding: 0 var(--space-4) calc(var(--space-6) + env(safe-area-inset-bottom));
    border: none;
    border-start-start-radius: var(--radius-card);
    border-start-end-radius: var(--radius-card);
    background: var(--panel);
    color: var(--text-main);
    box-shadow: 0 -8px 40px rgba(0, 0, 0, 0.28);
    overflow-y: auto;
    overscroll-behavior: contain;
}

.sheet::backdrop {
    background: rgba(0, 0, 0, 0.45);
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
}

/*
 * It slides up.
 *
 * The movement is what says where it came from, and a sheet that simply
 * appears reads as a page that changed rather than a drawer that opened.
 * Under the reduced-motion rule like everything else here.
 */
@media (prefers-reduced-motion: no-preference) {
    .sheet[open] {
        animation: sheet-rise 0.22s var(--ease-out);
    }

    .sheet[open]::backdrop {
        animation: sheet-fade 0.22s var(--ease-out);
    }
}

@keyframes sheet-rise {
    from {
        transform: translateY(12%);
        opacity: 0.4;
    }
}

@keyframes sheet-fade {
    from {
        opacity: 0;
    }
}

/*
 * The grip, which is now the handle and the way out.
 *
 * Drawn as the same four-pixel bar every phone shows, and sized as a control:
 * the visible line stays 4px and the button around it is the full touch floor,
 * spanning the sheet, because it is what a thumb reaches for and what a screen
 * reader activates to close. Padding rather than a bigger bar — growing the
 * line would draw attention to a thing whose whole job is to be recognised and
 * then ignored.
 */
.sheet__grip {
    display: block;
    inline-size: 100%;
    min-block-size: var(--touch-min);
    padding: 0;
    border: 0;
    cursor: grab;
    /*
     * Pinned, because it scrolls away otherwise.
     *
     * Measured: with a long sheet scrolled two hundred pixels down, the handle
     * was above the visible area and the drag that should have closed the
     * sheet landed on a paragraph instead. It is the way out — leaving it at
     * the top of the content means somebody halfway down a list has no visible
     * way out at all. It needs the panel colour rather than none, or the
     * content runs underneath it.
     */
    position: sticky;
    inset-block-start: 0;
    z-index: 1;
    background: var(--panel);
    /* The browser must not claim the vertical gesture for scrolling, or the
       drag never gets a single pointermove. */
    touch-action: none;
}

.sheet__grip::before {
    content: "";
    display: block;
    inline-size: 2.25rem;
    block-size: 4px;
    margin: var(--space-3) auto;
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--text-dim) 40%, transparent);
}

.sheet__grip:active {
    cursor: grabbing;
}

/*
 * The ring hugs the bar, not the button.
 *
 * The button is the width of the sheet because that is the target; outlining
 * it draws a rectangle across the whole top, which reads as a box somebody
 * forgot to style rather than as focus. A keyboard still needs to see where it
 * is, so the ring moves to the visible line.
 */
.sheet__grip:focus-visible {
    outline: none;
}

.sheet__grip:focus-visible::before {
    outline: 2px solid var(--accent);
    outline-offset: 4px;
}

/* Nothing to see when the dialog itself holds focus on open. */
.sheet:focus-visible {
    outline: none;
}

/*
 * While a finger is on it there is no transition, because the sheet IS the
 * finger; the moment it lets go there is one, so the spring back and the throw
 * away are both movements rather than jumps.
 */
.sheet {
    transition: transform 0.22s var(--ease-out);
}

.sheet--dragging {
    transition: none;
}

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

.sheet__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
}

.sheet__title {
    margin: 0;
    font-size: 1.1875rem;
    font-weight: 700;
    letter-spacing: -0.015em;
}

.sheet__note {
    margin: var(--space-2) 0 0;
    font-size: 0.9375rem;
    line-height: 1.5;
    color: var(--text-dim);
}

.sheet__body {
    margin-block: var(--space-4) 0;
}

/*
 * A button in a sheet spans it.
 *
 * Measured in the exercise menu: "Warm-up" came out 146 pixels wide and
 * "Change exercise", right under it, 358 — two buttons doing the same kind of
 * job at two different sizes, one of them hugging the start edge with two
 * hundred pixels of nothing beside it. A pill sized to its own text is right
 * in a card, where it sits among other things; a sheet is one question, and
 * its answers are the width of it. That is what both phone platforms draw, and
 * it is also what makes them the same size as each other, which is what says
 * they are alternatives.
 *
 * Scoped to `.button` on purpose: the steppers, chips and result rows that
 * also live in sheets are not answers to the sheet and must keep their shape.
 */
.sheet__body > .button,
.sheet__body > .row > .button,
.sheet__body > .sets__actions > .button,
.sheet__body > .cardio-actions > .button,
.sheet__body > form > .button {
    inline-size: 100%;
}

/* Stacked, not side by side: two answers to one question read as a list. */
.sheet__body > .row,
.sheet__body > .cardio-actions {
    flex-direction: column;
}

/* And they need air between them, which a bare stack of buttons has none of. */
.sheet__body > .button + .button {
    margin-block-start: var(--space-2);
}

.sheet__foot {
    margin-block-start: 1rem;
    position: sticky;
    inset-block-end: 0;
    background: var(--panel);
    padding-block: 0.5rem;
}

.sheet__confirm {
    inline-size: 100%;
}

/*
 * A card you tap.
 *
 * The whole card is the target, not a button inside it: on a phone the thing
 * you touch is the thing you are looking at, and a card that shows a value and
 * then asks you to find a small control to change it is a page with a form on
 * it wearing a card.
 */
/*
 * A card somebody can press, drawn as a card.
 *
 * Measured: rgb(239,239,239), zero radius, one pixel of padding and no shadow
 * — which is not a design, it is the browser's default button chrome. This
 * rule removed the border and stopped there, so the one tappable card in the
 * application sat among rounded white panels looking like a grey box somebody
 * forgot. Same tokens as the row list it sits above, because they are the same
 * kind of thing: a surface you press to open something.
 */
.cardx--tappable {
    display: block;
    inline-size: 100%;
    text-align: start;
    border: none;
    font: inherit;
    color: inherit;
    cursor: pointer;
    background: var(--panel);
    border-radius: var(--radius-card);
    padding: var(--space-4);
    box-shadow: var(--card-shadow-raised);
    transition: transform 0.12s var(--ease-standard);
}

/* The number and the chevron on one line, the chevron at the end of it. */
.cardx--tappable .measure__body {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
}

.cardx--tappable:active {
    transform: scale(0.985);
}

.measure__label {
    margin: 0 0 0.15rem;
    font-size: 0.9375rem;
    color: var(--text-dim);
}

.measure .figure__value {
    font-size: 2.75rem;
}

.measure .figure__unit {
    font-size: 1.0625rem;
}

/*
 * What said the card does something, and what says it now.
 *
 * It was a line of accent text — "tap to change" — written when this was the
 * only pressable card in the application and there was no other way to say so.
 * There is one now, and it is on every other row that opens something: the
 * chevron. A sentence explaining an affordance is a sentence the rest of the
 * interface does not need, and reading it costs more than recognising the
 * arrow.
 *
 * It stays for the empty case only, where it is not explaining the control but
 * inviting the first reading: a dash and an arrow say "this does something"
 * and not "there is nothing here yet".
 */
.measure__hint {
    margin: var(--space-2) 0 0;
    font-size: 0.8125rem;
    color: var(--accent-text);
}

/*
 * The number inside the stepper reads left to right.
 *
 * The buttons themselves no longer need this: they used to read "+0.5", a
 * leading neutral followed by digits, which the bidi algorithm put at the
 * visual right in Arabic, Persian and Urdu — the increments came out as "0.5+"
 * and "1−". They are a lone glyph now and there is nothing left to reorder.
 *
 * The value between them still needs it. A quantity is a mathematical
 * expression rather than a phrase, and mathematics reads left to right in
 * every one of the twenty languages here.
 *
 * The row itself is deliberately NOT directed: minus and plus swap sides in a
 * right-to-left interface, which is what those platforms do and what a hand
 * that has used them expects.
 */
.pad__display {
    direction: ltr;
    unicode-bidi: isolate;
}

/*
 * The way back inside a sheet.
 *
 * Mirrored in a right-to-left interface by the chevron itself, not by a
 * transform: a bare ‹ is a character and the browser lays it out with the
 * paragraph, so it points the correct way without anything being told.
 */
.sheet__back {
    inline-size: 2.25rem;
    block-size: 2.25rem;
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--text-dim) 14%, transparent);
    color: var(--text-main);
    font-size: 1.25rem;
    line-height: 1;
    cursor: pointer;
}

/* The title takes the space the two round controls leave, and centres in it
   when there is a back button — the shape of a pushed page everywhere. */
.sheet__title {
    flex: 1;
    min-inline-size: 0;
}

.sheet__head:has(.sheet__back:not([hidden])) .sheet__title {
    text-align: center;
}

/*
 * The value on the right of a row.
 *
 * A row that leads somewhere says what is already there, so somebody scanning
 * the list knows which one to open without opening any of them. Tabular
 * figures because a column of them lines up, and dim because the title is the
 * thing being scanned.
 */
.rowx__value {
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* ==========================================================================
   The body map.

   The shape somebody thinks about their own body with, and the replacement for
   three screens that asked the same questions as lists and bar charts.
   ========================================================================== */

.bodymap {
    inline-size: 100%;
    max-inline-size: 15rem;
    block-size: auto;
    margin-inline: auto;
    display: block;
}

/*
 * The body under the parts.
 *
 * Filled rather than stroked, and the reason is the gaps: the shapes of an
 * anatomical figure do not tile a person, and without something behind them
 * the spaces between the abdominals read as holes rather than as skin.
 */
.bodymap__frame {
    fill: var(--body-base);
}

/*
 * The outline on top.
 *
 * A hairline that does not thicken when the drawing is scaled down, which is
 * what `non-scaling-stroke` is for: the figure is 1448 units tall and drawn at
 * roughly 300 pixels, so a stroke expressed in user units would come out at a
 * fifth of a pixel and disappear.
 */
.bodymap__edge {
    fill: none;
    stroke: var(--body-edge);
    stroke-width: 1;
    vector-effect: non-scaling-stroke;
    pointer-events: none;
}

/*
 * A region.
 *
 * The lit fraction rides in a custom property so one rule covers "not trained
 * at all" and "everything went here": the accent at an opacity the caller
 * decided. A region with nothing to say is still part of the body — drawn in
 * the same quiet fill as the frame — rather than a hole where a shape should
 * be.
 */
.bodymap__region {
    --lit: 0;
    fill: color-mix(in srgb, var(--accent) calc(var(--lit) * 100%), var(--body-base));
    transition: fill 0.2s var(--ease-standard);
}

.bodymap__region[role="button"] {
    cursor: pointer;
}

/*
 * The one being talked about.
 *
 * An outline rather than a different fill, because the fill already carries
 * how much the region was worked and a second meaning in the same channel is
 * two facts fighting over one colour.
 */
.bodymap__region--on,
.bodymap__region:focus-visible {
    outline: none;
    stroke: var(--accent);
    stroke-width: 2;
    vector-effect: non-scaling-stroke;
}

/* Front and back, side by side while they fit and stacked when they do not. */
.bodymap-pair {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.5rem;
    align-items: start;
}

/*
 * One figure on its own, when the other side has nothing to show.
 *
 * A bench press works nothing on the back, and drawing a second grey body
 * beside the lit one spends half the width saying "not here". Wider than a
 * half-column because it no longer has to share.
 */
.bodymap-one .bodymap {
    max-inline-size: 11rem;
}

.bodymap-pair__side {
    text-align: center;
}

.bodymap-pair__label {
    margin: 0.25rem 0 0;
    font-size: 0.8125rem;
    color: var(--text-dim);
}

/*
 * The body as a control.
 *
 * Centred and given room, because it is the thing the section is rather than
 * an illustration beside it.
 */
.bodypick {
    margin-block: 0.5rem 1rem;
    text-align: center;
}

.bodypick__hint {
    margin: 0.25rem 0 0;
    font-size: 0.875rem;
    color: var(--text-dim);
}

/*
 * The last reading, at the top of the sheet that changes it.
 *
 * First, because the question somebody opens a tape-measure reading with is
 * usually "what was it last time": a girth means nothing except against the
 * one before it.
 */
.girth-last {
    text-align: center;
    padding-block-end: 0.75rem;
    border-block-end: 1px solid var(--border);
}

.girth-last .figure {
    justify-content: center;
}

.girth-last .figure__value {
    font-size: 2.25rem;
}

.girth-last__when {
    margin: 0.2rem 0 0;
    font-size: 0.8125rem;
    color: var(--text-dim);
}

/*
 * The toast.
 *
 * Above the tab bar rather than over it: the bar is where a thumb lives, and a
 * message that lands on the button somebody is reaching for is a message that
 * gets pressed through. It sits above the sheet layer too, because saving from
 * inside a sheet is exactly when a confirmation is wanted.
 *
 * Centred and content-width, so a two-word confirmation is a two-word pill and
 * not a bar across the screen. `max-inline-size` keeps a long one inside the
 * gutters on the narrowest phone.
 *
 * `pointer-events: none` throughout: it is never a control and it must never
 * take a tap meant for what is underneath it.
 */
.toast {
    /*
     * Every one of these overrides a default the browser gives a popover: a
     * bordered, centred, auto-sized box in the middle of the screen. Without
     * them the confirmation is a framed rectangle sitting over the content.
     */
    position: fixed;
    inset: auto;
    inset-block-end: calc(var(--touch-target) + env(safe-area-inset-bottom, 0px) + var(--space-3));
    inset-inline: var(--gutter);
    border: 0;
    overflow: visible;

    margin-inline: auto;
    inline-size: fit-content;
    max-inline-size: 100%;
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-pill);

    background: var(--toast-bg);
    color: var(--toast-text);
    font-size: 0.875rem;
    line-height: 1.4;
    text-align: center;
    box-shadow: var(--shadow-lg);

    pointer-events: none;
    opacity: 0;
    transform: translateY(0.5rem);
    transition: opacity 0.18s var(--ease-standard), transform 0.18s var(--ease-out);
}

.toast--up {
    opacity: 1;
    transform: translateY(0);
}

/*
 * Between messages it is closed, which the popover already handles: an element
 * that is not `:popover-open` is `display: none` without anything here saying
 * so. What is left to say is that an empty one never shows even if something
 * opens it — the box lives in the document from the first message so a screen
 * reader has a region to watch, and it holds nothing for most of the
 * application's life.
 */
.toast:empty {
    display: none;
}
