/* ═══════════════════════════════════════════════════════════
   FuelMyRuns — application design system, in the SupraMonta
   theme: clean, light, calm, alpine. Lots of air, few
   elements, one idea per section.

   Single source of truth for the UI. Every page extends
   layout.html, which loads only this file: no Bootstrap, no
   per-page <style> blocks, no parallel component vocabulary.
   ═══════════════════════════════════════════════════════════ */

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   FONTS
   Served from this application rather than from Google.

   The stylesheet used to come from fonts.googleapis.com,
   in <head>, which made it a blocking request to a third
   party on every page load - and a classic script waits
   for the stylesheets declared before it, so when that
   host was slow or unreachable the page rendered and then
   sat there with none of its JavaScript having run.

   The theme's faces are Frutiger 57 Condensed for headings
   and Frutiger for text. Frutiger is licensed per
   installation, so it is asked for by name first - a
   machine that has it uses it - and the theme's own
   fallbacks follow, served from here: Archivo Narrow and
   Hanken Grotesk, both under the SIL Open Font License.
   Each is one variable file per subset covering every weight
   used, where the Barlow they replace took a file per weight.

   Only latin and latin-ext are here: latin-ext stays because
   names have accents in them.

   swap: text is readable in the fallback face from the first
   paint and re-renders when the real one arrives. Each is named
   by its fingerprint, ?v=, as every page names a file, and kept
   a year (#424), so only a visit without them cached swaps:
   tests/test_static_delivery.py fails when a font changes and
   its fingerprint here does not.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
@font-face {
    font-family: 'Archivo Narrow';
    font-style: normal;
    font-weight: 400 700;
    font-display: swap;
    src: url('../fonts/archivo-narrow-latin-ext.woff2?v=2eaf4bd71b54') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
    font-family: 'Archivo Narrow';
    font-style: normal;
    font-weight: 400 700;
    font-display: swap;
    src: url('../fonts/archivo-narrow-latin.woff2?v=e1097881272d') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: 'Hanken Grotesk';
    font-style: normal;
    font-weight: 400 700;
    font-display: swap;
    src: url('../fonts/hanken-grotesk-latin-ext.woff2?v=768af2923e0a') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
    font-family: 'Hanken Grotesk';
    font-style: normal;
    font-weight: 400 700;
    font-display: swap;
    src: url('../fonts/hanken-grotesk-latin.woff2?v=e9201eddf1d4') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   TOKENS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
:root {
    /*
       The one line that tells the browser which scheme the page is in. Every
       control the browser draws for itself - the date picker's icon and its
       popup, the scrollbars, the spinner on a number field, the background
       Chromium paints behind an autofilled input - follows it, and nothing
       else. Light here, for a browser that cannot tell light from dark in a
       token; the block after this one makes it the device's, or the
       athlete's choice (#403).
    */
    color-scheme: light;

    /* Sky White is every card; the page they sit on is a cool tint of it
       (#402), so that a card is a thing of its own, not a hairline on the
       same ground. The tints below are a card's insets and hovers. */
    --bg:            #EEF3F6;
    --surface:       #FCFDFF;
    /* An inset inside a card, and the alternate section ground. */
    --surface-2:     #F4F8FA;
    /* Hover, tinted fills, the elevation silhouette. */
    --surface-3:     #EDF3F7;
    --border:        #E6EDF2;
    --border-bright: #CFE0EA;
    /* What a field is told apart by (#384): its boundary is the only sign
       there is a box to type in, so it clears 3:1 (WCAG 1.4.11) on the card,
       on an inset and hovered - which --border-bright, at 1.33:1, did not.
       tests/test_colour_contrast.py measures it. */
    --input-border:  #6F8FA1;
    /* Tracks and an empty ring. */
    --track:         #E1EAEF;

    /* Turquoise Water is the primary voice: headings, primary buttons,
       links, key figures, the focus ring. White on it is 9.25:1. The words
       themselves are ink (--text, #402): an accent the same colour as the
       text it is in accents nothing. */
    --accent:            #0A4B6B;
    --accent-fill:       #0A4B6B;
    --accent-fill-hover: #0D6A97;
    --on-accent:         #FCFDFF;
    /* Words that are a link: the accent in light; in dark, where the accent
       is first a fill, a paler blue of it (#403). */
    --link:              #0A4B6B;
    --link-hover:        #0D6A97;
    /* The lockup's disc and its ridge: Turquoise Water carrying Sky White,
       turned round in dark (#403). */
    --brand-disc:        #0A4B6B;
    --brand-ridge:       #FCFDFF;

    /* Mountain Rock, the one warm accent - as a fill, a border, a dot, never
       as text: it is 2.27:1 on Sky White. Where it has to be words it is
       --rock-text, and a label *on* it is --on-rock. */
    --rock:          #D5A070;
    --rock-text:     #8A5A2E;
    --on-rock:       #3B2410;
    --rock-tint:     #F7EADC;
    --rock-hover:    #FAF1E8;
    /* Words on the tint: --on-rock's in light; a pale sand in dark, where
       the tint is deep and --on-rock is still the words on the fill (#403). */
    --on-rock-tint:  #3B2410;

    --text:          #172B36;
    --text-2:        #3F5966;
    /* Every hint, lamp detail and stat label on the site: 5.48:1 on the page.
       tests/test_colour_contrast.py measures it. */
    --muted:         #4F6570;

    /* Deep sand: what went wrong, and what destroys - error text, the delete
       card, signing out: the fill, and the words, darker, at 5.06:1 on the
       page and 5.55:1 on Sky White. */
    --missed:        #A3652C;
    --missed-text:   #965822;

    /* Traffic lights - the Strava lamps and the account's health dot - keep
       the colours everybody already reads: red, yellow, green. The lights
       themselves are bright; the words beside them are the darker shade of
       each, which clears AA on a card and on an inset (tests/
       test_colour_contrast.py). The state is always in words too, so colour
       is never the only cue. */
    --light-green:   #22A55A;
    --light-yellow:  #F5B82E;
    --light-red:     #E03B3B;
    --green-text:    #1E7B45;
    --yellow-text:   #8C5A00;
    --red-text:      #B42318;
    /* A line drawn on the yellow light - the Pantry's triangle's "!" - at
       3:1 or more on it: the yellow's words in light, a card's ground in
       dark, where its words are as light as the yellow itself. */
    --on-light-yellow: #8C5A00;
    /* Not yet, or not known: an outline rather than a colour. */
    --future:        #AFC4CF;

    /* Roles (#402). Done and needs a look, as words on their tints and as
       fills: the words clear AA on their tint and on a card, a fill 3:1 on
       a card and on a track (tests/test_colour_contrast.py). */
    --success-text:  #1E7B45;
    --success-fill:  #22A55A;
    --success-tint:  #E7F5EC;
    --warning-text:  #8C5A00;
    --warning-fill:  #9A3412;
    --warning-tint:  #FDF4DE;
    /* Short (#424): the red role's words on its own tint. */
    --danger-tint:   #FDECEA;
    /* What a dish is made of, each its colour wherever it is said - a chip's
       tint and words, a progress bar's fill - and never by colour alone:
       its name and its figure are always beside it. Energy is the accent. */
    --energy-tint:   #E6F0F5;
    --carb:          #B8740F;
    --carb-text:     #8A5400;
    --carb-tint:     #FCF1DE;
    --protein:       #C8475B;
    --protein-text:  #A0303F;
    --protein-tint:  #FBEAEC;
    --fat:           #2F8F7C;
    --fat-text:      #1F6B5C;
    --fat-tint:      #E3F3EF;
    --fibre:         #5E8F32;
    --fibre-text:    #456E1E;
    --fibre-tint:    #EDF5E4;

    --font-head:     'Frutiger 57 Condensed', 'Archivo Narrow', 'Arial Narrow', system-ui, sans-serif;
    --font-body:     'Frutiger', 'Hanken Grotesk', system-ui, sans-serif;

    --nav-h:         64px;
    --radius-sm:     12px;
    --radius-md:     16px;
    --radius-lg:     32px;
    --pill:          999px;

    --shadow-panel:  0 24px 60px rgba(10, 75, 107, 0.09);
    --shadow-card:   0 1px 2px rgba(23, 43, 54, 0.06), 0 4px 14px rgba(23, 43, 54, 0.04);
    /* What a modal dialog dims the page with (#579). */
    --scrim:         rgba(23, 43, 54, 0.45);

    /* The bar: Sky White, a little see-through, over what scrolls under it.
       --chrome is what the browser paints its own bar with, the same ground
       (layout.html's theme-color, and the manifest's). */
    --chrome:        #FCFDFF;
    --nav-glass:     rgba(252, 253, 255, 0.88);

    /* Sign in with Google and with Apple, as their guidelines draw them:
       Google's light theme and Apple's black here, Google's dark theme and
       Apple's white in dark (#403). The four colours of Google's G are its
       own in both. */
    --google-fill:       #FFFFFF;
    --google-fill-hover: #F4F5F6;
    --google-edge:       #DADCE0;
    --google-edge-hover: #C9CCD0;
    --google-words:      #3C4043;
    --google-blue:       #4285F4;
    --google-green:      #34A853;
    --google-yellow:     #FBBC05;
    --google-red:        #EA4335;
    --apple-fill:        #000000;
    --apple-fill-hover:  #1A1A1A;
    --apple-words:       #FFFFFF;

    /* A save landing on its card (.is-saved): a ring of greens running round
       it, and a glow, bright on either ground. */
    --saved-sweep:   #34C759, #A3E635, #00C7A3, #30D158, #7CF5B2, #34C759;
    --saved-glow:    rgba(52, 199, 89, 0.45);
    /* What a mask is drawn with: only its alpha counts. */
    --mask-ink:      #000000;

    --gutter:        clamp(20px, 5vw, 56px);
    --max-w:         1180px;
    /* Side padding that lines a full-width bar's contents up with the centred
       page column: the gutter, or more once the window is wider than it. */
    --edge:          max(var(--gutter), calc((100% - var(--max-w)) / 2 + var(--gutter)));

    --ease-out:      cubic-bezier(0.22, 1, 0.36, 1);
}

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   DARK (#403, ADR-0101)
   "Alpine night": a deep lake-water ground, cool blue-white
   words, Turquoise Water brightened to a glacier blue so it is
   still the primary voice, Mountain Rock still the warm one.
   Only colour and shadow change; every role above keeps its
   name.

   Each token that differs is light-dark(light, dark): the one
   the page uses is whichever scheme it is in - the device's
   (color-scheme: light dark), or the one chosen with the bar's
   button, which the server writes on <html> as data-theme. The
   first value of each is the token's above, which a browser
   without light-dark() uses alone, light as before.
   tests/test_colour_tokens.py holds the two to each other, and
   tests/test_colour_contrast.py measures both schemes.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
@supports (color: light-dark(Canvas, CanvasText)) {
    :root {
        color-scheme: light dark;

        --bg:                light-dark(#EEF3F6, #061B26);
        --surface:           light-dark(#FCFDFF, #0D2C3B);
        --surface-2:         light-dark(#F4F8FA, #0A2735);
        --surface-3:         light-dark(#EDF3F7, #0E3244);
        --border:            light-dark(#E6EDF2, #16394B);
        --border-bright:     light-dark(#CFE0EA, #245067);
        --input-border:      light-dark(#6F8FA1, #6A8FA3);
        --track:             light-dark(#E1EAEF, #173B4D);

        --accent:            light-dark(#0A4B6B, #5BB0D4);
        --accent-fill:       light-dark(#0A4B6B, #5BB0D4);
        --accent-fill-hover: light-dark(#0D6A97, #86C8E4);
        --on-accent:         light-dark(#FCFDFF, #061B26);
        --link:              light-dark(#0A4B6B, #86C8E4);
        --link-hover:        light-dark(#0D6A97, #B3DDEE);
        --brand-disc:        light-dark(#0A4B6B, #FCFDFF);
        --brand-ridge:       light-dark(#FCFDFF, #0A4B6B);

        --rock-text:         light-dark(#8A5A2E, #E0B489);
        --rock-tint:         light-dark(#F7EADC, #3A2A1C);
        --rock-hover:        light-dark(#FAF1E8, #2A1F15);
        --on-rock-tint:      light-dark(#3B2410, #F0CFAE);

        --text:              light-dark(#172B36, #EAF2F6);
        --text-2:            light-dark(#3F5966, #A9C3D1);
        --muted:             light-dark(#4F6570, #8FAFC0);

        --missed:            light-dark(#A3652C, #C98A4E);
        --missed-text:       light-dark(#965822, #D69A62);

        --light-green:       light-dark(#22A55A, #2FBF6B);
        --light-red:         light-dark(#E03B3B, #E5484D);
        --green-text:        light-dark(#1E7B45, #5ED38C);
        --yellow-text:       light-dark(#8C5A00, #F2C14E);
        --on-light-yellow:   light-dark(#8C5A00, #0D2C3B);
        --red-text:          light-dark(#B42318, #FF8F86);
        --future:            light-dark(#AFC4CF, #4E7285);

        --success-text:      light-dark(#1E7B45, #5ED38C);
        --success-fill:      light-dark(#22A55A, #2FBF6B);
        --success-tint:      light-dark(#E7F5EC, #10372A);
        --warning-text:      light-dark(#8C5A00, #F2C14E);
        --warning-fill:      light-dark(#9A3412, #F0855A);
        --warning-tint:      light-dark(#FDF4DE, #3A2C10);
        --danger-tint:       light-dark(#FDECEA, #3D1A16);
        --energy-tint:       light-dark(#E6F0F5, #0E3244);
        --carb:              light-dark(#B8740F, #E0A23C);
        --carb-text:         light-dark(#8A5400, #F2C27A);
        --carb-tint:         light-dark(#FCF1DE, #3A2A12);
        --protein:           light-dark(#C8475B, #EE7A8C);
        --protein-text:      light-dark(#A0303F, #F6A9B4);
        --protein-tint:      light-dark(#FBEAEC, #3D1E26);
        --fat:               light-dark(#2F8F7C, #4FC1A9);
        --fat-text:          light-dark(#1F6B5C, #8ADFCC);
        --fat-tint:          light-dark(#E3F3EF, #0F3A33);
        --fibre:             light-dark(#5E8F32, #8CC456);
        --fibre-text:        light-dark(#456E1E, #B7DE8E);
        --fibre-tint:        light-dark(#EDF5E4, #22341A);

        /* A shadow on a dark ground is darker, not tinted. */
        --shadow-panel:      0 24px 60px light-dark(rgba(10, 75, 107, 0.09), rgba(0, 0, 0, 0.32));
        --shadow-card:       0 1px 2px light-dark(rgba(23, 43, 54, 0.06), rgba(0, 0, 0, 0.3)), 0 4px 14px light-dark(rgba(23, 43, 54, 0.04), rgba(0, 0, 0, 0.22));
        --scrim:             light-dark(rgba(23, 43, 54, 0.45), rgba(0, 0, 0, 0.6));
        --chrome:            light-dark(#FCFDFF, #061B26);
        --nav-glass:         light-dark(rgba(252, 253, 255, 0.88), rgba(6, 27, 38, 0.82));

        --google-fill:       light-dark(#FFFFFF, #131314);
        --google-fill-hover: light-dark(#F4F5F6, #1F2021);
        --google-edge:       light-dark(#DADCE0, #8E918F);
        --google-edge-hover: light-dark(#C9CCD0, #A8ABA9);
        --google-words:      light-dark(#3C4043, #E3E3E3);
        --apple-fill:        light-dark(#000000, #FFFFFF);
        --apple-fill-hover:  light-dark(#1A1A1A, #E8E8ED);
        --apple-words:       light-dark(#FFFFFF, #000000);
    }

    /* Chosen with the bar's button, whatever the device is in. */
    :root[data-theme="light"] { color-scheme: light; }
    :root[data-theme="dark"]  { color-scheme: dark; }
}

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

/* The hidden attribute means hidden, whatever display a class gives the
   element: .btn is inline-flex, which the browser's own [hidden] rule loses
   to. */
[hidden] { display: none !important; }

/* A link to a part of a page - /menus#shopping, or back to a day of a menu
   after a form - lands below the sticky navbar rather than under it; and,
   while a shopping list's status line says something over the list (#579,
   base.js's --sticky-said-h), below that line too, so that what the
   keyboard reaches is never under it. */
html { scroll-padding-top: calc(var(--nav-h) + 0.5rem + var(--sticky-said-h, 0px)); }

/*
   Present to the browser, absent from the page.

   Not display:none or visibility:hidden, which take an element out of the
   accessibility tree and out of a password manager's reckoning - which is the
   one thing the element using this needs to stay in.
*/
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

body {
    background-color: var(--bg);
    color: var(--text);
    font-family: var(--font-body);
    font-size: 16px;
    line-height: 1.55;
    min-height: 100vh;
    -webkit-font-smoothing: antialiased;
    /* The hero bleeds to the window's edges by way of 100vw, which counts the
       scrollbar; clip rather than hidden, so no scroll container is made. */
    overflow-x: clip;
}

/* The heading voice: condensed, slightly open, never shouting. */
.page-title,
.card-header,
.navbar-brand,
.stat__value,
.legal-text h2,
.linked__label,
.landing__title,
.landing__give-title {
    font-family: var(--font-head);
}

a { color: var(--link); text-underline-offset: 0.18em; }
a:hover { color: var(--link-hover); }

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   SKIP LINK
   The first thing in the tab order, and invisible until it
   has focus. Positioned rather than hidden: display:none or
   visibility:hidden would take it out of the tab order too,
   which is the one thing it exists to be in.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.skip-link {
    position: fixed;
    top: 0.5rem;
    left: 0.5rem;
    z-index: 300;
    padding: 0.6rem 1.1rem;
    background: var(--accent-fill);
    border-radius: var(--pill);
    color: var(--on-accent);
    font-weight: 600;
    font-size: 0.9rem;
    text-decoration: none;
    /* Out of sight, still in the document and still focusable. */
    transform: translateY(-200%);
    transition: transform 0.15s var(--ease-out);
}

.skip-link:focus { transform: translateY(0); color: var(--on-accent); }

/* It is the target of the skip link, so it takes focus - without a ring of its
   own, which would draw a box around the whole page. */
main:focus { outline: none; }

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   NAVBAR
   Sticky and translucent, a hairline below it, ghost pill
   links, and the one teal pill - the account.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.navbar {
    height: var(--nav-h);
    background: var(--nav-glass);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    border-bottom: 1px solid var(--border);
    display: flex;
    align-items: center;
    padding: 0 var(--edge);
    gap: 2rem;
    position: sticky;
    top: 0;
    z-index: 100;
}

/* The lockup: the disc on the left, the wordmark on the right, 10px apart.
   Clear space of half the disc's diameter is the padding around it. */
.navbar-brand {
    font-size: 25px;
    font-weight: 600;
    letter-spacing: 0.008em;
    line-height: 1;
    color: var(--text);
    text-decoration: none;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 15px 0;
    flex-shrink: 0;
}

.navbar-brand:hover { color: var(--text); }

.brand-icon {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: var(--brand-disc);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

/* The ridge: one open stroke, rounded, no fill - the elevation profile the
   dashboard's header rests on, in miniature. */
.brand-icon svg {
    width: 17px;
    height: 17px;
    fill: none;
    stroke: var(--brand-ridge);
    stroke-width: 2.4;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.navbar-nav {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 0.25rem;
    list-style: none;
    flex: 1;
}

.navbar-nav a {
    font-weight: 500;
    font-size: 0.95rem;
    color: var(--text-2);
    text-decoration: none;
    padding: 0.5rem 1rem;
    border-radius: var(--pill);
    transition: color 0.15s, background 0.15s;
}

.navbar-nav a:hover { color: var(--text); background: var(--surface-3); }

/* The section the page is in, as the server marks it (#384). */
.navbar-nav a[aria-current] { color: var(--text); background: var(--surface-3); }

/* ── User menu ─────────────────────────────────────────── */
.user-menu { position: relative; margin-left: auto; }
/* A <details> (#384): its summary is the pill, with no marker of its own. */
.user-menu > summary { list-style: none; }
.user-menu > summary::-webkit-details-marker { display: none; }

/* The navbar's one teal pill, 44px tall, the least a target here is
   (ADR-0080), as the theme button beside it is (ADR-0146). */
.u-btn {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    min-height: 44px;
    background: var(--accent-fill);
    border: none;
    border-radius: var(--pill);
    padding: 0 0.95rem 0 0.35rem;
    color: var(--on-accent);
    cursor: pointer;
    transition: background 0.15s;
}

.u-btn:hover { background: var(--accent-fill-hover); }

.avatar {
    /* The anchor for .avatar__dot, which hangs off its corner. */
    position: relative;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background: var(--surface);
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-head);
    font-weight: 700;
    font-size: 0.875rem;
    letter-spacing: 0.04em;
    color: var(--text);
    flex-shrink: 0;
}

.user-name {
    font-weight: 600;
    font-size: 0.92rem;
    color: var(--on-accent);
}
/* A shopping list's copy kept offline, which leaves the name out (sw.js):
   the pill is its initials alone, as between 769 and 1100px. */
.user-name:empty { display: none; }

.chevron {
    width: 12px;
    height: 12px;
    color: var(--on-accent);
    transition: transform 0.2s var(--ease-out);
    flex-shrink: 0;
}

.user-menu[open] .u-btn .chevron { transform: rotate(180deg); }

.u-dropdown {
    position: absolute;
    top: calc(100% + 10px);
    right: 0;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: 0.4rem;
    /* The account status lines are sentences - "Strava API: daily limit
       reached" - and anything narrower wraps every one of them. */
    min-width: 250px;
    /* Wide enough for a sentence a line, and no wider than the screen. */
    width: max-content;
    max-width: min(340px, calc(100vw - 2 * var(--gutter)));
    box-shadow: var(--shadow-panel);
    z-index: 200;
    transform-origin: top right;
}

/* What a closed <details> holds is not rendered at all - out of the tab
   order and the accessibility tree, as a menu nobody can see must be
   (#384) - so it fades in as it opens, each time. */
.user-menu[open] .u-dropdown { animation: u-dropdown-in 0.2s var(--ease-out); }

@keyframes u-dropdown-in {
    from { opacity: 0; transform: translateY(6px); }
}

.u-dropdown a,
.u-dropdown button {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
    text-align: left;
    font-family: var(--font-body);
    font-size: 0.92rem;
    font-weight: 500;
    color: var(--text-2);
    text-decoration: none;
    background: none;
    border: none;
    padding: 0.6rem 0.8rem;
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: background 0.12s, color 0.12s;
}

.u-dropdown a:hover,
.u-dropdown button:hover { background: var(--surface-3); color: var(--text); }
.u-dropdown a[aria-current] { color: var(--text); background: var(--surface-3); }

.u-dropdown svg { width: 15px; height: 15px; flex-shrink: 0; }

.u-dropdown .logout { color: var(--missed-text); }
/* --rock-text hovered: --missed-text on the tint is 4.22:1 (#384). */
.u-dropdown .logout:hover { background: var(--rock-hover); color: var(--rock-text); }

.dropdown-divider { border-top: 1px solid var(--border); margin: 0.35rem 0; }

/* ── Mobile ────────────────────────────────────────────── */
/* Meals in the bar itself, on a phone only (#400). */
.navbar-quick { display: none; }

.hamburger {
    display: none;
    flex-direction: column;
    gap: 5px;
    background: none;
    border: none;
    cursor: pointer;
    padding: 8px;
    margin-left: auto;
    border-radius: var(--pill);
}

.hamburger span {
    display: block;
    width: 22px;
    height: 2px;
    background: var(--text);
    border-radius: var(--pill);
    transition: transform 0.2s var(--ease-out), opacity 0.2s;
}

.hamburger.open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.hamburger.open span:nth-child(2) { opacity: 0; }
.hamburger.open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* Under the bar wherever the page is scrolled to (#384): in the flow it
   opened at the top of a long page, off screen, with the focus in it. */
.mobile-nav {
    display: none;
    flex-direction: column;
    position: fixed;
    top: var(--nav-h);
    left: 0;
    right: 0;
    z-index: 99;
    max-height: calc(100dvh - var(--nav-h));
    overflow: auto;
    overscroll-behavior: contain;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    box-shadow: var(--shadow-panel);
    padding: 0.75rem var(--gutter) 1rem;
    gap: 0.2rem;
}

/* Only where its toggle is: a phone turned into a tablet's width closes it. */
@media (max-width: 768px) {
    .mobile-nav.open { display: flex; }
}

.mobile-nav a,
.mobile-nav button {
    display: block;
    width: 100%;
    text-align: left;
    background: none;
    border: none;
    cursor: pointer;
    font-family: var(--font-body);
    font-weight: 500;
    font-size: 1rem;
    color: var(--text-2);
    text-decoration: none;
    padding: 0.65rem 1rem;
    border-radius: var(--pill);
    transition: background 0.15s, color 0.15s;
}

.mobile-nav a:hover,
.mobile-nav button:hover { background: var(--surface-3); color: var(--text); }
.mobile-nav a[aria-current] { color: var(--text); background: var(--surface-3); }

/* The profile entry carries the health dot and the state beside it, so these
   lay out as rows rather than as blocks of text. */
.mobile-nav a { display: flex; align-items: center; gap: 0.55rem; }

.mobile-nav__detail {
    margin-left: auto;
    font-size: 0.875rem;
    font-weight: 500;
    color: var(--muted);
    text-align: right;
}

/* ── Light or dark (#403, ADR-0101) ─────────────────────── */
/* Last in the bar: a circle of its own, the moon to go dark, the sun to come
   back to light. Its name is "Dark mode", pressed or not. 44px, as the user
   menu's pill beside it is (ADR-0146). */
.theme-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 44px;
    height: 44px;
    padding: 0;
    border: 1.5px solid var(--border-bright);
    border-radius: 50%;
    background: none;
    color: var(--text);
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s;
}

.theme-toggle:hover { background: var(--surface-3); border-color: var(--input-border); }
.theme-toggle svg,
.mobile-nav__theme svg { flex: none; width: 20px; height: 20px; }
.theme-toggle__sun,
[aria-pressed="true"] > .theme-toggle__moon { display: none; }
[aria-pressed="true"] > .theme-toggle__sun { display: block; }

/* Before signing in the bar holds the lockup and this, at its far end. */
.navbar-brand + .theme-toggle { margin-left: auto; }

/* Its room kept, unseen and out of reach, until base.js has it working: made
   to appear after the first paint, it moved the bar's other items aside. */
.theme-toggle[data-theme-waiting] { visibility: hidden; }

/* In the site menu, a row like the others, saying On or Off at its end. */
.mobile-nav .mobile-nav__theme { display: flex; align-items: center; gap: 0.55rem; }

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   ACCOUNT HEALTH
   The traffic lamp at a distance, one dot: green working,
   yellow partly, red not, a dashed outline for not known yet.
   One source for the states: services/account_health.py.
   The dot is never the whole message - every one has its
   state in words beside it or in its label.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.health-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    flex-shrink: 0;
    background: transparent;
    border: 1.5px dashed var(--future);
}

.health-dot--lg { width: 14px; height: 14px; }

.health-dot[data-state="ok"]        { background: var(--light-green);  border: 0; }
.health-dot[data-state="attention"] { background: var(--light-yellow); border: 0; }
.health-dot[data-state="broken"]    { background: var(--light-red);    border: 0; }

/* Nothing asked yet, or nothing to report: an outline, not a colour. A lamp
   that guesses is worse than one that says it does not know. */
.health-dot[data-state="unknown"],
.health-dot[data-state="neutral"] { background: transparent; border: 1.5px dashed var(--future); }

/* On the avatar, as a badge on its corner, ringed in the pill's own teal so it
   sits on the avatar rather than looking like a hole in it. */
.avatar__dot {
    position: absolute;
    right: -2px;
    bottom: -2px;
    width: 12px;
    height: 12px;
    box-shadow: 0 0 0 2px var(--accent-fill);
}

.avatar__dot[data-state="unknown"],
.avatar__dot[data-state="neutral"] { background: var(--surface); }

/* ── The checklist behind the dot, in the dropdown ──────── */
.u-health { padding: 0.55rem 0.8rem 0.2rem; }

.u-health__title {
    display: block;
    font-size: 0.875rem;
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--rock-text);
    margin-bottom: 0.55rem;
}

.u-health__list {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
}

.u-health__item {
    display: flex;
    align-items: flex-start;
    gap: 0.55rem;
    font-size: 0.875rem;
    color: var(--text-2);
}
/* Level with the label's first line, however many its sentence takes. */
.u-health__item > .health-dot { margin-top: 0.3em; }

/* Each item's sentence under its label, in words (#384). */
.u-health__text { display: flex; flex-direction: column; gap: 0.1rem; }
.u-health__detail { font-size: 0.875rem; color: var(--muted); }

/* ── The same reading as one line, at the top of a page ─── */
.health-strip {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    padding: 1rem 1.25rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    text-decoration: none;
    transition: background 0.15s, border-color 0.15s;
}

.health-strip:hover { background: var(--surface-2); border-color: var(--border-bright); }

.health-strip__text {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
    flex: 1;
}

.health-strip__label {
    font-family: var(--font-head);
    font-weight: 600;
    font-size: 1.15rem;
    letter-spacing: 0.014em;
    line-height: 1.08;
    color: var(--text);
}

.health-strip[data-state="ok"]        .health-strip__label { color: var(--green-text); }
.health-strip[data-state="attention"] .health-strip__label { color: var(--yellow-text); }
.health-strip[data-state="broken"]    .health-strip__label { color: var(--red-text); }

.health-strip__detail {
    font-size: 0.875rem;
    color: var(--muted);
    /* One line: the detail is a sentence and this strip is a summary. The
       whole of it is on the profile page, one click away. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* On a phone the whole sentence, on as many lines as it takes (#384): cut
   at 320px it lost the part that says what to do (WCAG 1.4.10). */
@media (max-width: 768px) {
    .health-strip__detail { white-space: normal; overflow: visible; }
}

.health-strip__cta {
    display: flex;
    align-items: center;
    gap: 0.2rem;
    flex-shrink: 0;
    font-weight: 600;
    font-size: 0.92rem;
    color: var(--accent);
}

.health-strip__cta svg { width: 15px; height: 15px; }

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   PAGE
   Left-aligned, one column of generous air, 1180px at most.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.page-content {
    padding: clamp(32px, 5vw, 64px) var(--gutter) clamp(48px, 8vw, 110px);
    max-width: var(--max-w);
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: 1.75rem;
}

.page-content--narrow { max-width: calc(520px + 2 * var(--gutter)); }

.page-title {
    font-weight: 600;
    font-size: clamp(28px, 4.2vw, 56px);
    letter-spacing: 0.014em;
    line-height: 1.02;
    color: var(--accent);
}

.page-subtitle {
    color: var(--text-2);
    font-size: 1.1rem;
    margin-top: 0.6rem;
    max-width: 48ch;
}

/*
   The signature motif: a mountain elevation profile, sitting low under the
   page's heading and running into a tinted band below it. Static, and
   decoration only - the markup marks it aria-hidden.

   Full bleed from inside the centred column: the margins pull the block out
   to the window's edges and the padding puts its words back where the column
   is, so the heading lines up with everything below it.
*/
.page-hero {
    --profile-h: clamp(56px, 9vw, 120px);
    --band-h: 28px;
    position: relative;
    /* Its own stacking context, so the silhouette behind the words stays
       behind them and in front of the page. */
    isolation: isolate;
    margin-inline: calc(50% - 50vw);
    padding: 0 calc(50vw - 50%) calc(var(--profile-h) + var(--band-h));
}

.page-hero::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: var(--band-h);
    background: var(--surface-3);
    z-index: -1;
}

.page-hero__profile {
    position: absolute;
    left: 0;
    bottom: calc(var(--band-h) - 1px);
    width: 100%;
    height: var(--profile-h);
    z-index: -1;
}

.page-hero__profile .fill  { fill: var(--surface-3); }
.page-hero__profile .ridge { fill: none; stroke: var(--border-bright); stroke-width: 1.5; }

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   CARD
   White, a hairline, 16px corners. No hover lift: a card is
   a place, not a control.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.card {
    /* Positioned for the glow a save leaves on it (.is-saved, below). */
    position: relative;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-card);
    /* Clipped to its corners without being a scroll container, so that a
       sticky part inside - the Week tab's days (#365) - sticks to the page. */
    overflow: hidden;
    overflow: clip;
}

.card-header {
    font-weight: 600;
    font-size: clamp(20px, 2.2vw, 25px);
    letter-spacing: 0.014em;
    line-height: 1.08;
    color: var(--accent);
    padding: 1.4rem 1.75rem 1rem;
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

.card-section { padding: 1.25rem 1.75rem 1.5rem; }
.card-section + .card-section { border-top: 1px solid var(--border); }

/* Pushed to the end of a header, where a lamp row would sit. Body type,
   because it is a link to somewhere rather than a title. */
.card-header__link {
    margin-left: auto;
    font-family: var(--font-body);
    font-size: 0.9rem;
    font-weight: 600;
    letter-spacing: 0;
    color: var(--accent);
    text-decoration: none;
    flex-shrink: 0;
}

.card-header__link:hover { text-decoration: underline; }

/* The one card that destroys things rather than saving them. Edged and
   titled in deep sand so it cannot be mistaken for the cards above it at a
   glance - the button inside it already is. */
.card--danger { border-color: var(--rock); }
.card--danger .card-header { color: var(--missed-text); }

/* Elsewhere .btn-danger is the quiet one - disconnecting Strava can be undone
   by connecting again. The button that deletes an account should not have to
   be hovered to look like what it is. */
.card--danger .btn-danger {
    background: var(--rock-tint);
    border-color: var(--missed);
    color: var(--on-rock-tint);
}

.card--danger .btn-danger:hover {
    background: var(--rock);
    border-color: var(--rock);
    color: var(--on-rock);
}

.card-footer {
    padding: 1rem 1.75rem;
    border-top: 1px solid var(--border);
    color: var(--text-2);
    font-size: 0.92rem;
    background: var(--surface-2);
}

/* Small, bold, open, in the accent's text shade, led by a sand dot. */
.eyebrow {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    font-family: var(--font-body);
    font-size: 0.875rem;
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--rock-text);
    margin-bottom: 0.85rem;
}

.eyebrow::before {
    content: '';
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--rock);
    flex-shrink: 0;
}

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   BUTTONS
   Pills. Primary is teal with a white label; secondary is
   white with a sand edge and a sand-text label.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.btn {
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 0.97rem;
    min-height: 52px;
    padding: 0 1.6rem;
    border-radius: var(--pill);
    border: 1.5px solid transparent;
    cursor: pointer;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.55rem;
    line-height: 1.2;
    /* Onto a second line inside the pill where the label does not fit, never
       cut off (#384, WCAG 1.4.10): "Agree to the current wording" at 320px,
       "Plan my session · Coming soon" in a dashboard column. */
    white-space: normal;
    text-align: center;
    max-width: 100%;
    padding-block: 0.45rem;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
    position: relative;
    overflow: hidden;
}

.btn svg { flex-shrink: 0; }

/* On the narrowest phones a label wider than its line wraps, rather than be
   cut off (#372, WCAG 1.4.10): the button grows taller, never wider. */
.btn:disabled, .btn.is-busy, .btn[aria-disabled="true"] { opacity: 0.55; pointer-events: none; }

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

.btn-primary:hover {
    background: var(--accent-fill-hover);
    border-color: var(--accent-fill-hover);
    color: var(--on-accent);
}

.btn-secondary {
    background: var(--surface);
    color: var(--rock-text);
    border-color: var(--rock);
}

.btn-secondary:hover {
    background: var(--rock-hover);
    border-color: var(--rock);
    color: var(--rock-text);
}

/* Quiet, until hovered: disconnecting is undone by connecting again. */
.btn-danger {
    background: var(--surface);
    color: var(--text-2);
    border-color: var(--border-bright);
}

.btn-danger:hover {
    background: var(--rock-hover);
    border-color: var(--missed);
    color: var(--rock-text);
}

/* The small one - date presets and the like - still a pill, still readable. */
.btn-ghost {
    background: var(--surface);
    border-color: var(--border-bright);
    color: var(--text-2);
    font-size: 0.88rem;
    min-height: 36px;
    padding: 0.3rem 0.95rem;
}

.btn-ghost:hover { background: var(--surface-3); color: var(--text); border-color: var(--border-bright); }

/* The small button (#372): a row's actions - beside a dish, a meal, a list,
   a heading. Two sizes only, this and the regular one a form submits with,
   and this one still 44px tall, the least any target here is. */
.btn-sm {
    min-height: 44px;
    /* Room above and below for a label on two lines (#384). */
    padding: 0.3rem 0.9rem;
    font-size: 0.875rem;
    gap: 0.4rem;
}

/*
   The preset whose range the date fields currently hold.

   Solid fill, matching the segmented control's checked state, because these
   presets are the same kind of single choice. An outline would not do: an
   outlined "active" is indistinguishable from whichever button the pointer
   happens to rest on.
*/
.btn-ghost.is-active {
    background: var(--accent-fill);
    border-color: var(--accent-fill);
    color: var(--on-accent);
}

/* Hovering the chosen one shifts its teal rather than repainting it as a
   plain hover, so it never stops looking chosen. */
.btn-ghost.is-active:hover {
    background: var(--accent-fill-hover);
    border-color: var(--accent-fill-hover);
    color: var(--on-accent);
}

.btn-block { width: 100%; }

.btn__spinner {
    display: none;
    width: 14px;
    height: 14px;
    border: 2px solid currentColor;
    border-top-color: transparent;
    border-radius: 50%;
    animation: spin 0.7s linear infinite;
}

.btn.is-busy .btn__spinner { display: inline-block; }

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

/*
   Buttons at the foot of a form: each as wide as its own label, wrapping when
   the card is too narrow to hold them.

   Not .actions, which is a grid of cells for rows of similar short labels:
   a long label in one of those cells wraps inside its pill (#384), which is
   no way to lay out a form's buttons.
*/
.form-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    align-items: center;
}

/* Action grid used by the dashboard sections. */
.actions {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(210px, 100%), 1fr));
    gap: 0.75rem;
}

.actions--wide > .btn-primary { grid-column: 1 / -1; }

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   STRAVA TRAFFIC LAMP
   Three lights, one lit: red, yellow, green, as a traffic
   light is. Static: data does not move.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.lamp-row {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    margin-left: auto;
}

.lamp {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 9px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--pill);
    flex-shrink: 0;
}

.lamp__light {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--track);
}

/* Only the light matching the current state is lit. */
.lamp[data-state="disconnected"] .lamp__light--red   { background: var(--light-red); }
.lamp[data-state="expired"]      .lamp__light--amber { background: var(--light-yellow); }
.lamp[data-state="connected"]    .lamp__light--green { background: var(--light-green); }

.lamp-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }

.lamp-label {
    font-size: 0.88rem;
    font-weight: 600;
    color: var(--text-2);
}

.lamp-detail {
    font-size: 0.875rem;
    color: var(--muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.lamp-state--connected    .lamp-label { color: var(--green-text); }
.lamp-state--expired      .lamp-label { color: var(--yellow-text); }
.lamp-state--disconnected .lamp-label { color: var(--red-text); }

/* The API lamp beside it, on its own set of states. Same three lights: what
   they mean here is whether Strava is answering, not whether this account is
   connected to it. */
.lamp[data-state="unavailable"]  .lamp__light--red   { background: var(--light-red); }
.lamp[data-state="rate_limited"] .lamp__light--amber { background: var(--light-yellow); }
.lamp[data-state="available"]    .lamp__light--green { background: var(--light-green); }

.lamp-state--available    .lamp-label { color: var(--green-text); }
.lamp-state--rate_limited .lamp-label { color: var(--yellow-text); }
.lamp-state--unavailable  .lamp-label { color: var(--red-text); }

/* Two lamps, stacked, pushed to the end of the card header together. */
.lamp-stack {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    margin-left: auto;
    align-items: flex-start;
    font-family: var(--font-body);
}

.lamp-stack .lamp-row { margin-left: 0; }

.lamp-eyebrow {
    font-size: 0.875rem;
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--rock-text);
}

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   STAT STRIP
   Key figures in teal, big and condensed, labels small.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.stats {
    display: grid;
    /* Narrow enough that the three figures stay one row on a phone: wrapped,
       two and one leave a hole in the strip. */
    grid-template-columns: repeat(auto-fit, minmax(min(92px, 100%), 1fr));
    gap: 1px;
    background: var(--border);
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
}

.stat { background: var(--surface); padding: 1.25rem 1.75rem; }

.stat__value {
    font-size: clamp(28px, 3vw, 38px);
    font-weight: 600;
    letter-spacing: 0.008em;
    line-height: 1;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}

.stat__label {
    margin-top: 0.4rem;
    font-size: 0.875rem;
    font-weight: 500;
    color: var(--muted);
}

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   FORMS
   Pill inputs, labels in plain words above them.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.field { display: flex; flex-direction: column; gap: 0.45rem; }

.field label {
    font-size: 0.92rem;
    font-weight: 600;
    color: var(--text);
}

.field input,
.field select,
.field textarea {
    width: 100%;
    min-height: 48px;
    background: var(--surface);
    border: 1px solid var(--input-border);
    border-radius: var(--pill);
    color: var(--text);
    font-family: var(--font-body);
    font-size: 1rem;
    padding: 0.6rem 1.15rem;
    transition: border-color 0.15s, background 0.15s;
}

.field textarea { border-radius: var(--radius-md); min-height: 6rem; }

/* background-color, not background: the shorthand would also reset the
   drop-down's arrow, which is drawn as a background image (#277). */
.field input:hover,
.field select:hover,
.field textarea:hover { background-color: var(--surface-2); }

.field input:focus,
.field select:focus,
.field textarea:focus {
    border-color: var(--accent);
    background-color: var(--surface);
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

.field input::placeholder { color: var(--muted); }

/*
   One pill for every field (#277). A drop-down is otherwise drawn by the
   browser - in Safari a short, square box beside the pills - so it is drawn
   here instead, its arrow two gradients in the theme's own colour; and a
   number box loses the browser's step arrows, which no other box has.
*/
.field select {
    appearance: none;
    -webkit-appearance: none;
    padding-right: 2.75rem;
    background-image:
        linear-gradient(45deg, transparent 50%, var(--muted) 50%),
        linear-gradient(135deg, var(--muted) 50%, transparent 50%);
    background-position:
        calc(100% - 1.4rem) 50%,
        calc(100% - 1.4rem + 5px) 50%;
    background-size: 5px 5px;
    background-repeat: no-repeat;
    cursor: pointer;
}

/* A file picker: the browser's own grey "Choose File" button, off-centre
   inside the pill, becomes a smaller pill within it (#277, the Plan page).
   Two rules, not one list: a browser that does not know one of the two
   pseudo-elements would drop a list naming both. */
.field input[type="file"] {
    display: flex;
    align-items: center;
    padding: 0.3rem 1.15rem 0.3rem 0.35rem;
    color: var(--text-2);
    cursor: pointer;
}

.field input[type="file"]::file-selector-button {
    margin-right: 0.9rem;
    padding: 0.5rem 1.1rem;
    border: 0;
    border-radius: var(--pill);
    background: var(--surface-3);
    color: var(--text);
    font: inherit;
    font-size: 0.92rem;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
}

.field input[type="file"]::-webkit-file-upload-button {
    margin-right: 0.9rem;
    padding: 0.5rem 1.1rem;
    border: 0;
    border-radius: var(--pill);
    background: var(--surface-3);
    color: var(--text);
    font: inherit;
    font-size: 0.92rem;
    font-weight: 600;
    cursor: pointer;
}

.field input[type="file"]:hover::file-selector-button {
    background: var(--accent-fill);
    color: var(--on-accent);
}

.field input[type="number"] { appearance: textfield; -moz-appearance: textfield; }
.field input[type="number"]::-webkit-inner-spin-button,
.field input[type="number"]::-webkit-outer-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

.field input[type="date"]::-webkit-calendar-picker-indicator { cursor: pointer; }

/* 14px at the least (#402), a little closer set than the body's text. */
.field-hint { font-size: 0.875rem; line-height: 1.45; color: var(--muted); }

/* The one checkbox in the application: explicit consent for health data.
   A row rather than a bare input so the label is part of the target - the
   box itself is a small hit area, and a consent control that is fiddly to
   untick is not "as easy to withdraw as to give". align-items: start keeps
   the box level with the first line when the label wraps on a phone. */
.check-row {
    display: flex;
    align-items: start;
    gap: 0.65rem;
    cursor: pointer;
    font-size: 0.97rem;
    color: var(--text);
}

.check-row input[type="checkbox"],
.check-row input[type="radio"] {
    flex: none;
    width: 1.15rem;
    height: 1.15rem;
    /* Not a field's box, in a field as Show password is: level with its words. */
    min-height: 0;
    padding: 0;
    margin: 0.2rem 0 0;
    accent-color: var(--accent-fill);
    cursor: pointer;
}

.check-row input[type="checkbox"]:focus-visible,
.check-row input[type="radio"]:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* What a box does, beneath it and level with its words: the box's width
   and the row's gap. */
.check-row__hint { margin: 0.3rem 0 0 1.8rem; }

/* One choice of three, each with what it would agree to beneath it (#378):
   shown only under the option picked, so that the one notice on screen is
   the one the choice would record. A browser without :has() shows them all,
   which hides nothing. */
.choices { display: grid; gap: 0.85rem; }
.choice__notice { margin: 0.35rem 0 0 1.8rem; }
.choice__notice > * + * { margin-top: 0.5rem; }
@supports selector(:has(*)) {
    .choice:not(:has(input:checked)) .choice__notice { display: none; }
}

/* Holds its line before the script fills it in, so the buttons below do not
   shift down once the echoed date appears. */
.field-hint[data-date-display] { margin: 0; min-height: 1.1em; }

.form-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
    gap: 1.1rem;
}

/* The boxes of a row line up whatever is said under them (#381): a field
   starts at the top of its cell, so that a hint or an error under one box
   pushes none of the others in its row down - as the foot of the cell, the
   rule #277 had for a label that wraps, did. Labels are short enough not to
   wrap at any width the grid puts fields side by side at, which the layout
   test holds (tests/test_layout.py, MEASURE). */
.form-grid > .field { justify-content: flex-start; }

.form-error {
    color: var(--missed-text);
    font-size: 0.92rem;
    min-height: 1.3rem;
}

/* ── A save, landing (#277) ─────────────────────────────── */
/*
   Where a save landed, after the page reloads with what was kept: a green
   gradient flowing once round the card's edge and a soft glow inward from
   it, after the light Siri now wakes with, then gone. base.js's flashSaved
   puts .is-saved on the card. With reduced motion the global rule makes
   every animation instant, so .is-saved--still is a plain green border
   for a moment instead.
*/
@property --save-angle {
    syntax: "<angle>";
    inherits: false;
    initial-value: 0deg;
}

.card.is-saved {
    animation: save-glow 1.9s ease-out forwards;
}

.card.is-saved::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    padding: 3px;
    border-radius: inherit;
    pointer-events: none;
    background: conic-gradient(
        from var(--save-angle),
        var(--saved-sweep)
    );
    /* Only the ring: the padding box, less the content box. */
    -webkit-mask: linear-gradient(var(--mask-ink) 0 0) content-box, linear-gradient(var(--mask-ink) 0 0);
    -webkit-mask-composite: xor;
    mask: linear-gradient(var(--mask-ink) 0 0) content-box exclude, linear-gradient(var(--mask-ink) 0 0);
    animation: save-ring 1.9s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}

@keyframes save-ring {
    0%   { --save-angle: 0deg;   opacity: 0; }
    12%  { opacity: 1; }
    70%  { opacity: 1; }
    100% { --save-angle: 300deg; opacity: 0; }
}

@keyframes save-glow {
    0%   { box-shadow: inset 0 0 0 transparent; }
    25%  { box-shadow: inset 0 0 40px var(--saved-glow); }
    100% { box-shadow: inset 0 0 0 transparent; }
}

.card.is-saved--still {
    border-color: var(--light-green);
    box-shadow: inset 0 0 0 2px var(--light-green);
}

/* ── Grouped controls ──────────────────────────────────── */
/*
   A fieldset is the only element that names a set of controls to a screen
   reader, and it arrives wearing a border, a margin and a min-width that no
   layout here asked for. Stripped back to the plain block the <div> before it
   was: the semantics are the point, the chrome is not.

   min-inline-size matters more than it looks. A fieldset's initial value is
   min-content, not the 0 every other block gets, which stops it shrinking
   inside a flex or grid parent - the reliable way to put a sideways scrollbar
   on a 320px phone. */
.group {
    min-inline-size: 0;
    margin: 0;
    padding: 0;
    border: 0;
}

/* The legend is laid out by the fieldset rather than as an ordinary block, and
   the browser pads it to clear a border we have just removed. The .eyebrow
   class on it carries the same type as the paragraph it replaced. */
.group > legend {
    padding: 0;
}

/* ── Chips (multi-select) ──────────────────────────────── */
.chips { display: flex; flex-wrap: wrap; gap: 0.5rem; }

.chip {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    min-height: 38px;
    padding: 0 1rem;
    border: 1px solid var(--border-bright);
    border-radius: var(--pill);
    background: var(--surface);
    color: var(--text-2);
    font-size: 0.9rem;
    cursor: pointer;
    user-select: none;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
}

.chip:hover { background: var(--surface-3); color: var(--text); }

.chip input { position: absolute; opacity: 0; pointer-events: none; }

/* Chosen, a chip is filled as every other chosen control is - the selected
   choice of a segmented control, the active date preset - not merely
   outlined, which read as hovered and left a ticked allergy easy to miss
   (#277). The fill and its label are the pair test_filled_control_contrast
   already holds to AA. */
.chip:has(input:checked) {
    background: var(--accent-fill);
    border-color: var(--accent-fill);
    color: var(--on-accent);
}

.chip:has(input:checked):hover { background: var(--accent-fill-hover); }

.chip__count { font-size: 0.875rem; color: var(--muted); }
.chip:has(input:checked) .chip__count { color: var(--on-accent); }

/* ── Segmented control (single choice) ─────────────────── */
.segmented {
    display: inline-flex;
    flex-wrap: wrap;
    padding: 4px;
    gap: 4px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    /* Not a pill: on a phone the choices wrap to a second row, and a pill
       two rows tall is a lozenge. The choices inside stay pills. */
    border-radius: 26px;
}

.segmented label {
    position: relative;
    padding: 0.45rem 1.05rem;
    border-radius: var(--pill);
    font-family: var(--font-body);
    font-size: 0.9rem;
    font-weight: 500;
    color: var(--text-2);
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
}

.segmented label:hover { color: var(--text); background: var(--surface-3); }
.segmented input { position: absolute; opacity: 0; pointer-events: none; }
.segmented label:has(input:checked) { background: var(--accent-fill); color: var(--on-accent); }

/* ── Linked provider accounts ──────────────────────────── */
/*
   The other half of the buttons below: what is already linked to this account,
   on the profile page. One row per provider, each with the address it reported
   and the day it was linked.
*/
.linked-list {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}

.linked {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    flex-wrap: wrap;
    padding: 0.85rem 1.1rem;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}

.linked__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

.linked__label {
    font-weight: 600;
    font-size: 1.1rem;
    letter-spacing: 0.014em;
    color: var(--text);
}

.linked__detail { font-size: 0.875rem; line-height: 1.45; color: var(--muted); }

/* ── Federated sign-in ─────────────────────────────────── */
.oauth { display: flex; flex-direction: column; gap: 0.7rem; margin-bottom: 1.4rem; }

/*
   Each provider dictates its own look, so these deliberately step outside the
   palette: a Google button that is teal, or an Apple button that is not
   black, reads as a counterfeit of the thing people are being asked to trust.
   The shape is the only thing of ours on them.
*/
.btn-oauth {
    width: 100%;
    justify-content: center;
    gap: 0.6rem;
    font-weight: 500;
    text-decoration: none;
}

.btn-oauth--google { background: var(--google-fill); border-color: var(--google-edge); color: var(--google-words); }
.btn-oauth--google:hover { background: var(--google-fill-hover); border-color: var(--google-edge-hover); color: var(--google-words); }

.btn-oauth--apple { background: var(--apple-fill); border-color: var(--apple-fill); color: var(--apple-words); }
.btn-oauth--apple:hover { background: var(--apple-fill-hover); border-color: var(--apple-fill-hover); color: var(--apple-words); }

/* The G, in its four colours. */
.btn-oauth__g--blue   { fill: var(--google-blue); }
.btn-oauth__g--green  { fill: var(--google-green); }
.btn-oauth__g--yellow { fill: var(--google-yellow); }
.btn-oauth__g--red    { fill: var(--google-red); }

/* "or", with a hairline running through it to separate the buttons from the
   form. */
.oauth-divider {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    margin-top: 0.5rem;
    color: var(--muted);
    font-size: 0.875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.16em;
}

.oauth-divider::before,
.oauth-divider::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--border);
}

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   FOCUS
   One ring, on everything that can take focus.

   :focus-visible rather than :focus, so the ring appears for
   a keyboard and not for a mouse click, which is what made
   ringing everything unattractive enough to skip.

   A solid Turquoise Water outline - 9.25:1 on the page - and
   outline follows the border radius of whatever it is drawn
   on, so one rule fits pills, cards and chips alike.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.btn:focus-visible,
.btn-oauth:focus-visible,
.u-btn:focus-visible,
.navbar-brand:focus-visible,
.navbar-nav a:focus-visible,
.navbar-quick:focus-visible,
.mobile-nav a:focus-visible,
.mobile-nav button:focus-visible,
.u-dropdown a:focus-visible,
.u-dropdown button:focus-visible,
.card-header__link:focus-visible,
.card-footer a:focus-visible,
.health-strip:focus-visible,
.hamburger:focus-visible,
.theme-toggle:focus-visible,
.skip-link:focus-visible,
.chip:has(input:focus-visible),
.segmented label:has(input:focus-visible),
.week-menu summary:focus-visible,
.week-menu__lineage summary:focus-visible,
.week-menu__shopping summary:focus-visible {
    outline: 2px solid var(--accent);
    /* Clear of the element rather than on top of it, so the ring reads on a
       filled button as well as an outlined one. */
    outline-offset: 3px;
}

/* A link in running text is not a component and has no padding to offset
   against; the ring sits tight to the words. */
.card-section a:focus-visible,
.field-hint a:focus-visible,
.message a:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   MESSAGES
   Tinted grounds, not colours of their own: teal-tinted for
   done, sand-tinted for what went wrong, the alternate ground
   for what is only information.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.message {
    display: none;
    padding: 0.9rem 1.2rem;
    border-radius: var(--radius-md);
    border: 1px solid var(--border);
    background: var(--surface-2);
    color: var(--text-2);
    font-size: 0.95rem;
}

/* A block, not a flex row: a message is a sentence, and a flex row would make
   each bold phrase, each run of text and each <code> in it a column of its
   own, squeezed side by side and cut off by the card. */
.message.is-visible { display: block; }
/* A form's one summary, under its buttons (#383, #386), apart from them. */
.message.is-visible + .form-actions,
.form-actions + .message.is-visible,
.btn + .message.is-visible { margin-top: 1rem; }
/* An empty one is a live region waiting for words (#383): rendered, so
   that it is in the accessibility tree and what is put in it is read out,
   but out of the flow - as .visually-hidden is - so that it takes no
   space, nor a flex column's gap, until it is. */
.message:empty {
    display: block;
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    border: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
}
/* A refused plan names each row it refused, a line each (#266), on the
   file's field (#383); and so does a refused file of recipes (#347). */
#planMessage,
#planFile-error,
#recipesMessage,
#recipesFile-error { white-space: pre-line; }

/* A path or a setting's name in a sentence is one long word; it breaks rather
   than running out of the card on a phone. */
.message code,
.legal-text p code { overflow-wrap: anywhere; }
.message--success { border-color: var(--border-bright); background: var(--surface-3); color: var(--text); }
.message--error   { border-color: var(--rock); background: var(--rock-tint); color: var(--on-rock-tint); }
.message--error a { color: var(--on-rock-tint); }
.message--info    { border-color: var(--border); }
/* Done, and what went wrong, look unlike what is only information (#383):
   a mark of their own - a tick, an exclamation - and their first words in
   bold, "Saved" or "Couldn't save" (base.js's say()). Not colour alone. */
.message--success:not(:empty),
.message--error:not(:empty) { position: relative; padding-left: 2.7rem; }
.message--success:not(:empty)::before,
.message--error:not(:empty)::before {
    position: absolute;
    left: 1rem;
    top: 0.95rem;
    display: grid;
    place-items: center;
    width: 1.15rem;
    height: 1.15rem;
    border-radius: 50%;
    font-size: 0.875rem;
    font-weight: 700;
    line-height: 1;
    color: var(--on-accent);
}
.message--success:not(:empty)::before { content: '\2713'; background: var(--green-text); }
.message--error:not(:empty)::before { content: '!'; background: var(--missed); }
.message__lead { font-weight: 700; }

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   MESSAGE CARDS (#532, ADR-0133)
   What waits on the athlete, in one stack at the foot of the
   page's content (templates/_notices.html): sticky to the
   window's foot, so that it floats while there is page under
   it and comes to rest above the footer - covering nothing for
   good, with script or without. Bottom right on a wide screen,
   the page's width on a phone, and above the Meals bar there.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.notices {
    position: sticky;
    bottom: calc(var(--bottom-bar, env(safe-area-inset-bottom)) + 0.75rem);
    z-index: 80;
    align-self: flex-end;
    width: min(26rem, 100%);
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.5rem;
}

.notice {
    position: relative;
    width: 100%;
    border: 1px solid var(--border-bright);
    border-inline-start: 4px solid var(--accent-fill);
    border-radius: var(--radius-md);
    background: var(--surface);
    box-shadow: var(--shadow-panel);
    color: var(--text-2);
    font-size: 0.95rem;
}

.notice--error { border-color: var(--rock); background: var(--rock-tint); color: var(--on-rock-tint); }
/* Done (base.js's floatSaid()): in the done role's colour, its first word
   "Saved" or the like in bold, so not by colour alone. */
.notice--success { border-inline-start-color: var(--success-fill); }

/* Minimized: in the pill, until it is shown again. Nothing to say yet:
   nothing to press either. */
.notice[data-minimized],
.notice.visually-hidden .notice__controls { display: none; }

/* The whole of the words is the link to where it is put right; its last
   words say so, as a link does. */
.notice__body {
    display: block;
    padding: 0.85rem 1rem;
    border-radius: calc(var(--radius-md) - 1px);
    color: inherit;
    text-decoration: none;
}

/* Room for the two controls in the top corner, and the words under them
   after: a card is not a column narrower all the way down for them. */
.notice:has(> .notice__controls:not([hidden])) .notice__body::before {
    content: '';
    float: right;
    width: calc(88px - 0.75rem);
    height: 2.2rem;
}

.notice__text p + p { margin-top: 0.5rem; }
.notice__text strong { color: inherit; }

.notice__cta {
    display: inline-block;
    margin-top: 0.35rem;
    color: var(--link);
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: 0.18em;
}

.notice__body:hover .notice__cta { color: var(--link-hover); }
.notice--error .notice__cta,
.notice--error .notice__body:hover .notice__cta { color: var(--on-rock-tint); }

.notice__controls { position: absolute; top: 0.25rem; right: 0.25rem; display: flex; }

.notice__control,
.notices__pill {
    display: inline-grid;
    place-items: center;
    min-width: 44px;
    min-height: 44px;
    border: 0;
    border-radius: var(--pill);
    background: transparent;
    color: var(--muted);
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
}

.notice__control:hover { background: var(--surface-3); color: var(--text); }
.notice--error .notice__control { color: var(--on-rock-tint); }
.notice--error .notice__control:hover { background: var(--rock-hover); }
.notice__control svg,
.notices__pill svg { width: 18px; height: 18px; }

.notice__action { margin: -0.35rem 0 0.85rem 1rem; }

.notices__pill {
    display: inline-flex;
    gap: 0.45rem;
    padding: 0 1.1rem;
    border: 1px solid var(--border-bright);
    background: var(--surface);
    box-shadow: var(--shadow-panel);
    color: var(--text);
    font: 600 0.95rem var(--font-body);
}

.notices__pill:hover { background: var(--surface-3); }

.notice__body:focus-visible,
.notice__control:focus-visible,
.notices__pill:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* A card with nothing to say yet, and a form's line whose words float as a
   card (base.js's floatSaid()): .visually-hidden over what each is drawn
   with - a card's box, a done or not-done line's mark, which would put it
   back in the page's flow. Still in the accessibility tree: the live
   regions that read their words out. */
.notice.visually-hidden,
.message.visually-hidden {
    position: absolute !important;
    width: 1px;
    padding: 0 !important;
    border: 0;
}

/* A stack of none but such: out of the page's flow, so no gap is kept for
   it. In :is(), whose list forgives a browser without :has(). */
:is(.notices:not(:has(.notice:not(.visually-hidden)))) { position: absolute; }

/* What a focused control scrolls clear of: the stack, measured by base.js,
   and the Meals bar on a phone. */
html { scroll-padding-bottom: calc(var(--bottom-bar, 0px) + var(--notices-h, 0px)); }

@media (max-width: 768px) {
    .notices { width: 100%; }
}

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   PROGRESS
   A teal bar on a pale track. It moves because the work does,
   not as decoration.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.progress {
    margin-top: 1rem;
    padding: 1rem 1.2rem;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}

.progress__head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    margin-bottom: 0.6rem;
    font-size: 0.9rem;
    color: var(--text-2);
}

.progress__count { font-variant-numeric: tabular-nums; color: var(--text); font-weight: 600; }

.progress__track {
    height: 8px;
    background: var(--track);
    border-radius: var(--pill);
    overflow: hidden;
}

.progress__bar {
    height: 100%;
    width: 0;
    background: var(--accent-fill);
    border-radius: var(--pill);
    transition: width 0.3s var(--ease-out);
}

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   REDUCED MOTION
   Somebody who has asked their system for less movement has
   usually asked for a reason - motion sickness, vertigo,
   migraine, a vestibular disorder.

   Near-zero rather than `none`, which is the usual spelling
   of this: a transition that is removed outright never
   fires transitionend, and a zero-length one still does -
   so anything that waits for a transition to finish goes on
   working.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
/* The scheme changed with the bar's button (#403): its colours ease over, for
   as long as base.js holds this class on <html>. Not for somebody who asked
   for less motion - and the device changing its own scheme needs none. */
@media (prefers-reduced-motion: no-preference) {
    :root.theme-changing,
    :root.theme-changing *,
    :root.theme-changing *::before,
    :root.theme-changing *::after {
        transition: background-color 0.2s, color 0.2s, border-color 0.2s, fill 0.2s, stroke 0.2s;
    }
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        transition-delay: 0s !important;
        scroll-behavior: auto !important;
    }

    /*
       ...except the one animation that is information rather than decoration.

       WCAG 2.3.3 is about motion that illustrates; a spinner says a request is
       still in flight, and every button that has one is disabled while it
       shows. Stopping it would leave a dead button and no way to tell whether
       anything was happening - so it keeps turning, at half speed. The wheels
       of a menu being made again (#341) are not exempt, deliberately: words
       beside them say the same, and they stand still as a clock face.
    */
    .btn__spinner {
        animation-duration: 1.4s !important;
        animation-iteration-count: infinite !important;
    }
}

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   SPACING
   The gaps between stacked blocks inside a card section.

   These exist as classes because they used to exist as
   style attributes - thirty of them across nine templates -
   and a style attribute is what forced 'unsafe-inline' into
   the Content-Security-Policy's style-src.

   Five steps and no more: a sixth would mean somebody is
   spacing by eye rather than picking from a scale.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.stack-xs { margin-top: 0.5rem; }
.stack-sm { margin-top: 0.75rem; }
.stack-md { margin-top: 1rem; }
.stack-lg { margin-top: 1.25rem; }
.stack-xl { margin-top: 1.5rem; }

/* The one that pushes what follows away rather than itself. */
.stack-below { margin-bottom: 1.25rem; }

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   LONG-FORM DOCUMENT
   The privacy notice is the only page here that is prose
   rather than controls, and the reset at the top of this
   file leaves bare h2, p, ul and table with no margins at
   all. One scoped block rather than five new component
   classes: everything below applies only inside .legal-text,
   so nothing it says can reach a card anywhere else.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.legal-text { max-width: 72ch; }

.legal-text h2 {
    font-size: clamp(22px, 2.4vw, 28px);
    font-weight: 600;
    letter-spacing: 0.014em;
    line-height: 1.08;
    margin-bottom: 0.85rem;
}

.legal-text h3 {
    font-size: 1.05rem;
    margin-top: 1.4rem;
    margin-bottom: 0.45rem;
}

.legal-text p { line-height: 1.6; }

.legal-text ul {
    /* The reset removed the padding the markers live in, so they would be
       clipped by the card's edge without this. */
    padding-left: 1.25rem;
    line-height: 1.6;
}

.legal-text li + li { margin-top: 0.5rem; }
.legal-text li::marker,
.week-menu__facts li::marker { color: var(--rock); }

/* A three-column table of what is held, why, and what permits it. It is wider
   than a phone at any readable size, so it scrolls sideways in its own box
   rather than making the whole page scroll - which would take the headings
   and paragraphs with it. */
.legal-text__scroll,
.table-scroll { overflow-x: auto; }

.legal-text table,
.data-table {
    border-collapse: collapse;
    width: 100%;
    min-width: 34rem;
    font-size: 0.92rem;
}

.legal-text th,
.legal-text td,
.data-table th,
.data-table td {
    border-bottom: 1px solid var(--border);
    padding: 0.65rem 0.75rem;
    text-align: left;
    vertical-align: top;
    line-height: 1.45;
}

.legal-text th,
.data-table th {
    font-size: 0.875rem;
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--rock-text);
}

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   ADMIN
   A tab strip of plain links - each tab is its own URL, so
   it survives a reload and works with script off - and the
   rendered markdown of the repository's own documents.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
/* An admin page is mostly tables and controls, not prose: it takes the page's
   width rather than .legal-text's 72ch, which hid the jobs table's last column
   behind a sideways scroll even on a desktop. Its sentences keep a readable
   measure. */
.admin-card { max-width: none; }
.admin-card p,
.admin-card ul { max-width: 80ch; }

/* A job's command under its name, as written: a path is case-sensitive, so it
   does not take the row heading's capitals and letter-spacing. And on a menu,
   a dish's cuisine and what it contains, each on a line of its own under its
   name. */
.admin-card th[scope="row"] .field-hint,
.week-menu__dish .field-hint { display: block; margin-top: 0.2rem; }
.admin-card th code { text-transform: none; letter-spacing: normal; }
/* A day that heads a row of the Capacity tables, on one line: in a row
   heading's capitals and spacing, "SUN, NOV 8, 2026" took three lines at
   768px (ADR-0145). The table scrolls in its own region instead. */
.admin-card__day { white-space: nowrap; }

/* The Capacity tab (FMR-083): figures as a list of terms, a meter for each of
   Strava's windows, and a line for growth. The line is an SVG polyline drawn
   with attributes and these classes - no inline style, which the content
   security policy refuses - in the accent, and the words beside it say what
   it draws. */
.capacity-figures {
    display: grid;
    grid-template-columns: minmax(10rem, max-content) 1fr;
    gap: 0.45rem 1.25rem;
    margin: 0;
}
.capacity-figures dt { font-weight: 600; color: var(--text); }
.capacity-figures dd { margin: 0; }
.capacity-meter {
    display: block;
    width: 100%;
    max-width: 20rem;
    height: 1rem;
    margin-bottom: 0.25rem;
}
.sparkline { display: inline-block; vertical-align: middle; overflow: visible; }
.sparkline__line {
    fill: none;
    stroke: var(--accent);
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}

@media (max-width: 640px) {
    .capacity-figures { grid-template-columns: 1fr; }
}

/* Rendered markdown. .legal-text already styles headings, paragraphs, lists
   and tables inside a card, so this adds only what a document needs beyond
   them. */
.admin-doc h2 { margin-top: 1.9rem; }
.admin-doc h3 { margin-top: 1.4rem; }

.admin-doc code {
    /* A path in a list item is one long word; it breaks rather than being
       cut off by the card on a phone. A fenced block keeps its lines: its
       whitespace is preserved, so this never applies there. */
    overflow-wrap: anywhere;
    background: var(--surface-3);
    padding: 0.05rem 0.35rem;
    border-radius: 6px;
    font-size: 0.9em;
}

/* A fenced block is wider than a phone at any readable size, so it scrolls in
   its own box rather than taking the page sideways with it. */
.admin-doc pre {
    overflow-x: auto;
    padding: 1rem 1.1rem;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    font-size: 0.875rem;
    line-height: 1.5;
}

.admin-doc pre code { background: none; padding: 0; }

.admin-doc blockquote {
    margin: 1.1rem 0;
    padding-left: 1rem;
    border-left: 3px solid var(--rock);
    color: var(--text-2);
}

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   SITE FOOTER
   One link, on every page, to the privacy notice. Quiet and
   out of the way: it has to be reachable from wherever
   somebody is, not compete with what they came for.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
.site-footer {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: 0.5rem 1.5rem;
    padding: 2rem var(--edge) 2.5rem;
    border-top: 1px solid var(--border);
    background: var(--surface-2);
    font-size: 0.88rem;
    color: var(--muted);
}

/* The version, on the far side from the notice: there to be quoted, not read. */
.app-version { font-variant-numeric: tabular-nums; }

/* A form narrow enough that two columns would be two half-width boxes. */
.form-grid--single { grid-template-columns: 1fr; }

/* Fields that come in pairs - username and address, first and last name; a
   place's name and size, its latitude and longitude - two to a row, so no
   field is left alone on a row of its own; one to a row on a phone (#277). */
.form-grid--two { grid-template-columns: repeat(2, minmax(0, 1fr)); }

@media (max-width: 640px) {
    .form-grid--two { grid-template-columns: 1fr; }
}

/* ── The one tab component (#386, _tabs.html) ──────────── */
/* A page's tabs, links each: pills in a row, the one shown filled. Under a
   page's title (Nutrition, Body, Profile); in the admin card; and, on a
   phone, the Meals area's bar at the foot of the screen. Before the
   responsive rules, whose phone padding for the admin card and whose bar
   for Meals are of the same specificity and must come after. */
.page-tabs__list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    list-style: none;
    margin-bottom: 1.25rem;
}
.page-tabs__tab {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 40px;
    padding: 0 1rem;
    border: 1px solid var(--border-bright);
    border-radius: var(--pill);
    background: var(--surface);
    color: var(--text-2);
    font-size: 0.95rem;
    font-weight: 500;
    text-decoration: none;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.page-tabs__tab:hover { background: var(--surface-3); color: var(--text); }
.page-tabs__tab[aria-current="page"] {
    background: var(--accent-fill);
    border-color: var(--accent-fill);
    color: var(--on-accent);
}
.page-tabs__tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The Meals area's: a line under them, as the tabs over a panel. */
.page-tabs--meals .page-tabs__list {
    padding: 0.25rem 0 0.75rem;
    margin-bottom: 0.5rem;
    border-bottom: 1px solid var(--border);
}
/* The admin pages': inside their card, its first section. */
.page-tabs--admin {
    padding: 0.9rem 1.75rem;
    border-bottom: 1px solid var(--border);
}
.page-tabs--admin .page-tabs__list { margin-bottom: 0; }
/* On a phone, every tab its share of a row: two or three to a row, four as
   two of two, so that no tab is left alone on a second row; a label of two
   words may take two lines. */
@media (max-width: 480px) {
    .page-tabs__list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .page-tabs--count-3 .page-tabs__list { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .page-tabs__tab { width: 100%; padding: 0.3rem 0.5rem; font-size: 0.9rem; line-height: 1.2; text-align: center; }
}

/* The landing page (#398): what SupraMonta does, three things it gives a
   runner - side by side where they fit, one under another where not - and
   the way in. */
.landing { max-width: 56rem; margin: 0 auto; }
.landing__title { font-size: clamp(1.6rem, 5vw, 2.2rem); margin: 0.5rem 0 0.75rem; }
.landing__lead { max-width: 40rem; font-size: 1.05rem; }
.landing__gives {
    list-style: none;
    padding: 0;
    margin: 1.5rem 0;
    display: grid;
    gap: 1rem;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
}
.landing__give { padding: 1.1rem 1.25rem; }
.landing__give-title { font-size: 1.05rem; margin: 0 0 0.35rem; }
.landing__give p { margin: 0; color: var(--muted); }

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
   RESPONSIVE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
/* Where the links and the account's button do not fit side by side -
   Menus and Pantry are in it with menus on (#342), and an admin has Admin -
   the links sit closer and then scroll within the bar, which needs no
   script, and a long name is cut short; below 769px the bar folds. */
@media (min-width: 769px) {
    /* Room inside for a link's focus ring, which the scrolling box would
       clip: the links are blocks, and the box pads them. */
    .navbar-nav { min-width: 0; overflow-x: auto; scrollbar-width: thin; padding: 6px; margin: -6px 0; scroll-padding-inline: 6px; }
    .navbar-nav li { flex: none; }
    .navbar-nav a { display: block; }
    /* The athlete's name (the owner's request) on one line, ending in an
       ellipsis where it is longer than the pill has room for: the whole of
       it is in the page, and is said as the summary's name. */
    .user-name { max-width: 10rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}
@media (min-width: 769px) and (max-width: 1200px) {
    .navbar { gap: 1rem; }
    .navbar-nav a { padding: 0.5rem 0.7rem; }
}
/* Narrower still, the account's button is its initials alone - and its
   name is still said, hidden as .visually-hidden hides words rather than
   taken out of what a screen reader is told the summary is. */
@media (min-width: 769px) and (max-width: 1100px) {
    .user-name {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        overflow: hidden;
        clip-path: inset(50%);
    }
}
@media (max-width: 768px) {
    .navbar-nav { display: none; }
    .user-menu  { display: none; }
    .hamburger  { display: flex; }
    .navbar     { gap: 0.5rem; }
    .page-content--profile { gap: 0.875rem; }

    /* Meals beside the site menu (#400): one tap from every page. */
    .navbar-quick {
        display: inline-flex;
        align-items: center;
        min-height: 44px;
        padding: 0 0.9rem;
        margin-left: auto;
        border-radius: var(--pill);
        color: var(--text-2);
        font-weight: 600;
        text-decoration: none;
    }
    .navbar-quick:hover { color: var(--text); background: var(--surface-3); }
    .navbar-quick[aria-current] { color: var(--text); background: var(--surface-3); }
    .navbar-quick + .hamburger { margin-left: 0; }
    /* Signed in, the bar is full: the site menu has it instead. */
    .hamburger ~ .theme-toggle { display: none; }

    .card-header,
    .card-section,
    .card-footer,
    .page-tabs--admin { padding-left: 1.25rem; padding-right: 1.25rem; }
    .stat { padding: 1rem 0.9rem; }

    .card-header { flex-wrap: wrap; }
    .lamp-row    { margin-left: 0; width: 100%; }
    .lamp-stack  { margin-left: 0; width: 100%; }

    /* The chevron and its word are the first thing to go: the whole strip is a
       link, so nothing is lost but the label saying so. */
    .health-strip__cta { display: none; }

    /* One line with an ellipsis is a fair trade in a card header on a desktop,
       where the lamp is an aside. On a phone the lamp stack is a full-width
       row of its own and the sentence is the whole of what it says, so it
       wraps instead of being cut off at "authorize strava to sta...". */
    .lamp-detail { white-space: normal; overflow: visible; }

    .linked { align-items: flex-start; }

    /* Where the jobs table is short of room, a command breaks inside the path
       so the table fits; on a wider screen it stays whole. */
    .admin-card th code { overflow-wrap: anywhere; }
}

/* ── Food preferences (#273) ───────────────────────────── */
/* One row per cuisine: its name, and a small like / no view / avoid control.
   Two columns where there is room, one on a phone. */
.cuisine-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 22rem), 1fr));
    gap: 0.5rem 1.5rem;
}

.cuisine-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
}

.cuisine-row__name { color: var(--text); font-size: 0.95rem; }

.segmented--compact { padding: 3px; gap: 3px; }
.segmented--compact label { padding: 0.3rem 0.75rem; font-size: 0.875rem; }

/* A tab's panel (#273): its cards one under the other. */
.tab-panel { display: flex; flex-direction: column; gap: 1.5rem; }

.row-actions { white-space: nowrap; }

/* A row deleted, in its place until it can no longer be taken back, and
   what is said of it under the table or list (#383, deleteWithUndo). */
.undo-row > td,
li.undo-row { color: var(--text-2); }
.undo-row > td .undo-row__said { margin-right: 0.75rem; }
li.undo-row,
div.undo-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 0.75rem;
}
.message[data-undo-region].is-visible { margin-top: 0.75rem; }

/* ── Meals tabs (#361) ─────────────────────────────────── */
/* Today, Week, Shopping, Pantry and, while menus are on, Recipes (#347): the
   one tab component (_tabs.html) under the title; on a phone, a bar fixed to
   the foot of the screen, within thumb's reach, with room left under the page
   so that it covers nothing. This week or Next week, and By aisle or By day,
   a smaller switch of the same look under it. */
.meals-switch__week {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 40px;
    padding: 0 1.1rem;
    border: 1px solid var(--border-bright);
    border-radius: var(--pill);
    background: var(--surface);
    color: var(--text-2);
    font-size: 0.95rem;
    font-weight: 500;
    text-decoration: none;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
}

.meals-switch__week:hover { background: var(--surface-3); color: var(--text); }

.meals-switch__week[aria-current="page"] {
    background: var(--accent-fill);
    border-color: var(--accent-fill);
    color: var(--on-accent);
}

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

/* This week or next: the smaller switch under the tabs. */
.meals-switch {
    display: flex;
    gap: 0.5rem;
    list-style: none;
    margin-bottom: 1rem;
}

.meals-switch__week { min-height: 36px; padding: 0 0.9rem; font-size: 0.9rem; }
/* The week's switch and, on Shopping, the list's (#373): side by side, and
   one under the other where they do not fit. */
.meals-switches { display: flex; flex-wrap: wrap; gap: 0 1.5rem; }

@media (max-width: 768px) {
    .page-tabs--meals {
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 90;
        background: var(--surface);
        border-top: 1px solid var(--border);
        padding: 0.4rem var(--gutter) calc(0.4rem + env(safe-area-inset-bottom));
    }

    .page-tabs--meals .page-tabs__list {
        display: grid;
        grid-template-columns: repeat(4, minmax(0, 1fr));
        gap: 0.35rem;
        padding: 0;
        margin: 0;
        border-bottom: 0;
    }

    .page-tabs--meals .page-tabs__tab { width: 100%; min-height: 44px; padding: 0 0.25rem; font-size: 0.95rem; }

    body:has(.page-tabs--meals) { padding-bottom: calc(4rem + env(safe-area-inset-bottom)); }

    /* A control reached from the keyboard scrolls into view above the bar. */
    html:has(.page-tabs--meals) { --bottom-bar: calc(4rem + env(safe-area-inset-bottom)); }
}

/* Four tabs in 320px (#384): "Shopping" fits its quarter in a smaller
   size, the target still 44px tall. */
@media (max-width: 360px) {
    .page-tabs--meals .page-tabs__tab { font-size: 0.875rem; }
}

/* ── Menus (FMR-072) ───────────────────────────────────── */
/* A week's menu, a day to a row (templates/_week_menu.html). Each day is a
   <details>: its summary says the day, its load and its target, and opens on
   what the target is made of, the meals and the notices. The list itself has
   no markers - the summary's own triangle says that a row opens. */
.week-menu { list-style: none; }

.week-menu__day { border-bottom: 1px solid var(--border); }
.week-menu > li:first-child .week-menu__day { border-top: 1px solid var(--border); }

.week-menu__day > summary {
    cursor: pointer;
    padding: 0.75rem 0;
    font-weight: 600;
    color: var(--text);
}

/* An open day keeps its last notice clear of the rule under it. */
.week-menu__day[open] { padding-bottom: 1.25rem; }

/* What the target is made of, the carbohydrate and the sessions: a short list,
   its markers in sand, as the privacy notice's are. */
.week-menu__facts {
    padding-left: 1.25rem;
    color: var(--text-2);
}

.week-menu__facts li + li { margin-top: 0.25rem; }

/* What a session's fuelling gives (#513): a line of its own under the
   session's, its link wrapping with its words. */
.week-menu__fuelling {
    display: block;
    margin-top: 0.25rem;
}

/* A dish's name, its cuisine and what it contains, never squeezed to a word
   a line by the figures beside it: its cell is never narrower than 10rem, or
   than the part of the table shown before it is scrolled where that part is
   narrower - 100cqi, the width of the .table-scroll the table is in. The cell
   is the first in its row, so it starts at that part's left edge, and this
   floor never carries its right edge past that part's. A browser without
   container units keeps the 10rem. */
.week-menu .table-scroll { container-type: inline-size; }

.week-menu__dish {
    min-width: 10rem;
    min-width: min(10rem, 100cqi);
    overflow-wrap: anywhere;
}

/* The dish is its row's header, and reads as the rest of the row does: none
   of a column heading's capitals, letter-spacing or size. overflow-wrap above
   breaks a word too long for the floor - a dish's name is the menu
   application's - rather than let it carry the cell past the part of the
   table a phone shows. */
.data-table th.week-menu__dish {
    font-size: inherit;
    font-weight: inherit;
    letter-spacing: normal;
    text-transform: none;
    color: inherit;
}

/* A figure and its unit stay on one line: "12 g", never "12" above "g";
   right-aligned in figures of one width, under a heading aligned the same
   way, so that a column's figures line up (#372). */
.week-menu__figure { white-space: nowrap; font-variant-numeric: tabular-nums; }
.data-table .week-menu__figure { text-align: right; }
.week-menu__held { display: block; white-space: normal; margin-top: 0.2rem; }

/* What made a week's menu, and why each day is what it is (FMR-073): a
   <details> each - the week's first under its heading, the line that says
   what made it inside (#364), a day's after its notices. The summary reads as the control it is, in the
   colour of a link, and is only as wide as its words, so that the empty
   row beside it does not open anything. */
.week-menu__lineage > summary,
.week-menu__technical > summary,
.week-menu__why > summary {
    cursor: pointer;
    width: fit-content;
    font-weight: 600;
    color: var(--accent);
}

.week-menu__lineage[open] > summary,
.week-menu__technical[open] > summary,
.week-menu__why[open] > summary { margin-bottom: 0.5rem; }

/* The technical details (#385), a fold inside the lineage: a smaller
   control, for whoever needs a version or a checksum. */
.week-menu__technical > summary { font-size: 0.875rem; }

/* A fact's label, in the colour of the words it labels rather than the
   list's softer one. */
.week-menu__facts strong { color: var(--text); }

/* A model's name, a figure's, a term's or a source is another application's,
   and can be one long word: it breaks, rather than run past the card's edge
   on a phone and be cut off there. */
.week-menu__made,
.week-menu__lineage,
.week-menu__why { overflow-wrap: anywhere; }

/* ── What a meal takes, and what an athlete does with it (Epic 09) ───── */
/* A meal's prep time follows its name in its header row, softer than it.
   The button that changes the meal is a form of its own, so that it works
   without script, in a row of its own under the meal's dishes, at the left,
   where a phone that scrolls the table sideways shows it. */
.week-menu__prep { font-weight: 400; color: var(--text-2); }

.week-menu__change { display: inline-block; }
.week-menu__change-row td { padding-top: 0.25rem; }

/* A row's actions (#372) - a dish's, or a meal's - on one line, centred on
   it, wrapping as a whole; never wider than the part of the table a phone
   shows before it is scrolled (100cqi, the .table-scroll's width, less the
   cell's padding), so that none of them is out of sight. An opened fold
   takes the row's full width beneath them. */
.week-menu__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 0.75rem;
    max-width: 16rem;
    max-width: calc(100cqi - 1.5rem);
}
.week-menu__actions > details[open] { flex: 1 0 100%; }
.week-menu__actions .week-menu__cooked,
.week-menu__actions .week-menu__eaten { margin: 0; max-width: none; }

/* The Week tab's card (#372): Change the rest of this week beside the
   heading from 1024px, at the card's right padding and on the heading's
   centre line - the two keep the same padding above and below, so that
   their centres meet - and under it, at the card's left edge, below. */
.card--action { display: grid; grid-template-columns: minmax(0, 1fr); }
.card--action > .card-section { grid-column: 1 / -1; }
.card-header__action { padding: 0 1.75rem; }
@media (max-width: 768px) {
    .card-header__action { padding: 0 1.25rem; }
}
@media (min-width: 1024px) {
    .card--action { grid-template-columns: minmax(0, 1fr) auto; align-items: center; }
    .card-header__action { padding: 1.4rem 1.75rem 1rem 0; }
}

/* A table scrolls sideways in its region where it is wider than the
   screen; on a phone, a line under it says so. */
.table-scroll[tabindex]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.week-menu__scroll-hint { display: none; margin: 0.75rem 0 0; }
@media (max-width: 700px) {
    .week-menu__scroll-hint { display: block; }
}

/* Where a form that changed a day sends the athlete back to: inside the
   day's <details>, which the browser opens, with its summary still in
   sight above it - below the sticky navbar, as the page's other anchors
   are (scroll-padding-top on html). */
.week-menu__facts { scroll-margin-top: 3.5rem; }

/* What a dish is made of, and its rating: a row across the table under the
   dish's own, opened from its summary, read as text rather than as one of
   the table's figures. */
.week-menu__recipe td { padding-top: 0; color: var(--text-2); }
.week-menu__recipe summary {
    cursor: pointer;
    width: fit-content;
    color: var(--accent);
}

/* A dish's recipe, on another site (#337): the link a button with its icon,
   and the site it opens beside it, softer - under it where the part of the
   table a phone shows is too narrow for both, and never wider than that
   part (100cqi, the .table-scroll's width, less the cell's padding), so
   that none of it is out of sight until the table is scrolled. A browser
   without container units keeps 16rem, which the narrowest phone shows. */
.week-menu__recipe-link {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.25rem 0.75rem;
    margin: 0.25rem 0;
    max-width: 16rem;
    max-width: calc(100cqi - 1.5rem);
}

.week-menu__recipe-link .btn {
    display: inline-flex;
    align-items: center;
}

.week-menu__icon {
    width: 1.1em;
    height: 1.1em;
    flex: none;
}

.week-menu__ingredients {
    padding-left: 1.25rem;
    margin: 0.4rem 0;
}

.week-menu__ingredients li + li { margin-top: 0.15rem; }

.week-menu__rating .group { margin: 0.5rem 0; }
.week-menu__rating legend { margin-bottom: 0.25rem; color: var(--text); }
.week-menu__rating legend .field-hint { display: block; }

/* A meal of several dishes adds them up, as the day's total does, in the
   weight of a total rather than of a dish. */
.week-menu__subtotal th,
.week-menu__subtotal td { font-weight: 600; }

/* A shopping list sent to Notes (#344, ADR-0075): a button with the share
   sheet's icon or the clipboard's, and what it said beside it - under it
   where there is no room. Its words never break inside one. */
.week-menu__notes {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.25rem 0.75rem;
    margin: 0.5rem 0 1rem;
}

.week-menu__notes .btn { overflow-wrap: normal; }
/* No gap after the button while there is nothing to say beside it: the
   status stays rendered, so that what it says is read out. */
.week-menu__notes { column-gap: 0; }
.week-menu__notes-said:not(:empty) { margin-left: 0.75rem; }

.week-menu__notes-label {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

/* An aisle's button at the right edge of its cards (#372, #579). */
.week-menu__notes-cell .week-menu__notes { margin: 0; justify-content: flex-end; }

/* What an aisle's button says wraps on a line of its own under the button,
   at its right, rather than push the button aside. Contained without its
   own line, it would have no width at all. */
.week-menu__notes-cell .week-menu__notes-said:not(:empty) { contain: inline-size; flex: 1 0 100%; margin-left: 0; text-align: right; }

/* On a phone, where a list is read in the shop, each button is 44px tall,
   and an aisle's is its icon alone, 44px square, at the head of its cards:
   its words are still its name, read out, as .visually-hidden keeps them. */
@media (max-width: 36rem) {
    .week-menu__notes .btn { min-height: 44px; }
    .week-menu__notes-cell .btn { min-width: 44px; }

    .week-menu__notes-cell .week-menu__notes-words {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        padding: 0;
        border: 0;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
}

/* The week's shopping list, and the list of what an athlete rated. */
.week-menu__shopping,
.week-menu__ratings { overflow-wrap: anywhere; }

.week-menu__ratings { list-style: none; }
.week-menu__ratings li {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.25rem 0.75rem;
    padding: 0.5rem 0;
    border-bottom: 1px solid var(--border);
}
.week-menu__ratings .field-hint { flex: 1 1 16rem; }

/* ── A menu being made again (#341, ADR-0074) ───────────────────── */
/*
   The wheel says a menu is being made, beside words that say it too. It
   turns while the page says so, for twenty minutes of one page view at most
   (1,334 turns of 0.9 s), and stands still as a clock face under reduced
   motion, or once the athlete asks for it: never a ring with a gap standing
   still, which reads as stuck. With script, the page stops it sooner, once
   it is late, ready or no longer looking.
   Not .btn__spinner: that one's exemption is for a disabled button.
*/
.menu-progress__wheel {
    display: inline-block;
    position: relative;
    width: 0.9em;
    height: 0.9em;
    margin-right: 0.45em;
    vertical-align: -0.1em;
    border: 2px solid currentColor;
    border-top-color: transparent;
    border-radius: 50%;
    animation: spin 0.9s linear 1334;
}
.menu-progress.is-late .menu-progress__wheel,
.menu-progress.is-stopped .menu-progress__wheel,
.menu-progress.is-ready .menu-progress__wheel,
.menu-progress.is-late .menu-progress__still,
.menu-progress.is-stopped .menu-progress__still,
.menu-progress.is-ready .menu-progress__still { display: none; }
.menu-wheels-still .menu-progress__wheel {
    animation: none;
    border-top-color: currentColor;
}
.menu-wheels-still .menu-progress__wheel::after {
    content: "";
    position: absolute;
    left: calc(50% - 1px);
    top: 12%;
    width: 2px;
    height: 40%;
    border-radius: 1px;
    background: currentColor;
}

.menu-progress--week p + p { margin-top: 0.4rem; }
/* Why a menu's portions changed (#407): the days under the sentence, a line
   each, their markers inside the box as a day's facts' are. */
.week-menu__moved ul { margin-top: 0.4rem; padding-left: 1.25rem; }
.week-menu__moved li + li { margin-top: 0.25rem; }
/* On Today the week's notices, and the line of what the menu is built for,
   follow the day's meals (#400): clear of the last of them. */
.week-menu ~ .message.is-visible,
.week-menu ~ .week-menu__built { margin-top: 1.25rem; }
.menu-progress__still { margin-top: 0.4rem; padding: 0.2rem 0.6rem; font-size: 0.875rem; }
.menu-progress__note { margin-top: 0.5rem; }
.menu-progress--place {
    margin: 0.35rem 0 0;
    color: var(--text-2);
    font-size: 0.9rem;
}
/* A cue inside a meal's table is in a cell across all seven columns: held to
   the part of the table a phone shows before it is scrolled, as the recipe
   link is, so that none of it is out of sight. */
.week-menu .menu-progress--place {
    max-width: 16rem;
    max-width: calc(100cqi - 1.5rem);
}
.menu-progress__ready .btn { margin-left: 0.25rem; padding: 0.2rem 0.6rem; font-size: 0.875rem; }
.week-menu__marker { font-weight: 400; color: var(--text-2); }

/* Where a meal's form lands, clear of the navbar like .week-menu__facts. */
.week-menu tbody[id] { scroll-margin-top: 3.5rem; }

/* A meal on its way: an outline at its edge, never dimmed. */
.week-menu tbody.is-pending > tr > :first-child { box-shadow: inset 3px 0 0 var(--future); }

/* What a new menu changed: the tint, a green edge and "New" in words. Static:
   the blanket reduced-motion rule would cut an animation short anyway. */
.week-menu tbody.is-new > tr > * { background: var(--surface-2); }
.week-menu tbody.is-new > tr > :first-child { box-shadow: inset 3px 0 0 var(--light-green); }
.week-menu__new,
.week-menu__unchanged {
    display: inline-block;
    margin-left: 0.5rem;
    padding: 0 0.45rem;
    border: 1px solid var(--light-green);
    border-radius: var(--pill);
    color: var(--green-text);
    font-size: 0.875rem;
    font-weight: 600;
    line-height: 1.5;
    vertical-align: 0.1em;
}
.week-menu__unchanged { border-color: var(--border-bright); color: var(--muted); font-weight: 400; }

@media (prefers-reduced-motion: reduce) {
    /* Not an exemption: the wheel stands still, as a clock face, and there is
       nothing to keep still. */
    .menu-progress__wheel { animation: none; border-top-color: currentColor; }
    .menu-progress__still { display: none; }
    .menu-progress__wheel::after {
        content: "";
        position: absolute;
        left: calc(50% - 1px);
        top: 12%;
        width: 2px;
        height: 40%;
        border-radius: 1px;
        background: currentColor;
    }
}

/* ── The pantry (#342, ADR-0076) ─────────────────────────────────── */
/*
   Cooked and Eaten under a meal, on the Menus page; then the Pantry page. A
   best-before date close or past says so in words beside a shape of two
   colours - never by colour alone. A shopping list's ticks are its cards'
   (#579, below).
*/
/* Never wider than the part of the table a phone shows, as a recipe's link
   (#337): what a mark took is read without scrolling the table. */
.week-menu__cooked,
.week-menu__eaten {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem 0.6rem;
    margin: 0.25rem 0;
    max-width: 16rem;
    max-width: calc(100cqi - 1.5rem);
}
.week-menu__mark { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 0.35rem 0.6rem; }
/* What a mark did, its tick on the first line and inside the text's edge,
   so that a line that wraps starts under the tick, not beside it (#372). */
.week-menu__marked { margin: 0; flex: 0 1 auto; min-width: 0; }
.week-menu__marked .week-menu__icon { display: inline; vertical-align: -0.2em; margin-right: 0.35rem; }
.week-menu__marked .week-menu__icon { color: var(--green-text); }
.week-menu__stranded { margin: 0; }
.week-menu__mark-row td { padding-top: 0.25rem; }
/* ── The Week tab, a day at a time (#365) ──────────────── */
/* The week's days, each a link to its own address, and the chosen day in
   full: stacked on a phone, side by side from 1024px, the days' list held
   in sight while the day scrolls. */
.week-days__list { list-style: none; margin: 0 0 1rem; padding: 0; display: grid; gap: 0.35rem; }
.week-days__day {
    display: grid;
    gap: 0.1rem;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    color: var(--text);
    text-decoration: none;
    overflow-wrap: anywhere;
}
.week-days__day:hover { background: var(--surface-3); }
.week-days__day:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.week-days__day[aria-current="page"] { border-color: var(--accent-fill); box-shadow: inset 4px 0 0 var(--accent-fill); }
.week-days__name { font-weight: 600; }
.week-days__facts,
.week-days__meals { color: var(--text-2); font-size: 0.875rem; }
@media (min-width: 1024px) {
    .week-split { display: grid; grid-template-columns: minmax(13rem, 17rem) minmax(0, 1fr); gap: 1.5rem; align-items: start; }
    .week-days { position: sticky; top: 5rem; max-height: calc(100vh - 6rem); overflow-y: auto; }
    .week-days__list { margin: 0; }
}

/* ── Today's meals as cards (#362) ─────────────────────── */
/* A card a meal (templates/_today_meals.html): its dishes, one line of
   figures each, Eaten - the one primary button - and the rest under "More".
   Nothing wider than the phone, so nothing scrolls sideways. */
.meal-cards { list-style: none; display: grid; gap: 0.75rem; padding: 0; margin: 1rem 0 0; }
.meal-card {
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 0.75rem 0.9rem;
    background: var(--surface);
}
.meal-card.is-pending { border-style: dashed; }
.meal-card__title { font-size: 1rem; margin: 0; }
/* The meal's name, or a dish's, with its recipe's button in the card's
   top-right corner (#372), on the name's centre line; under the name where
   the card is too narrow for both. */
.meal-card__head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.25rem 0.75rem; }
.meal-card > .meal-card__head { margin-bottom: 0.4rem; }
/* A meal's title that wraps keeps its Recipe on the centre line of its
   first line, as a dish's name does: the title starts as far down as half
   of what the 44px button is taller than its line (#567). */
.meal-card > .meal-card__head:has(> .meal-card__recipe) { align-items: flex-start; }
.meal-card > .meal-card__head:has(> .meal-card__recipe) > :first-child { padding-top: calc((44px - 1lh) / 2); }
.meal-card__head > :first-child { flex: 1 1 9rem; min-width: 0; }
.meal-card__recipe { flex: none; }
/* How a dish is made where it is not a button (#567): that it needs no
   cooking, or that it was taken out - a plain line, wrapped. */
.dish-how { margin: 0; overflow-wrap: anywhere; }
.meal-card__head > .dish-how { flex: 1 1 100%; }
/* Where each recipe is, at the card's foot, after an info icon on the
   line's first line. */
.meal-card__notes { list-style: none; margin: 0.5rem 0 0; padding: 0.5rem 0 0; border-top: 1px solid var(--border); }
.meal-card__notes li + li { margin-top: 0.25rem; }
.meal-card__info { display: inline; vertical-align: -0.2em; margin-right: 0.35rem; }
.meal-card__dish + .meal-card__dish { border-top: 1px solid var(--border); padding-top: 0.5rem; margin-top: 0.5rem; }
/* A card of three dishes or more, and a day's snacks, say their Mark as eaten above them (#482, ADR-0109, ADR-0150): the first dish is set off from it as from another. */
.meal-card > .week-menu__said + .meal-card__dish { border-top: 1px solid var(--border); padding-top: 0.5rem; margin-top: 0.5rem; }
.meal-card__name { margin: 0; font-weight: 600; overflow-wrap: anywhere; }
.meal-card__figures { margin: 0.1rem 0; }
/* How much of each of a snack's foods to count out, under its figures (ADR-0147). */
.meal-card__amounts { margin: 0.25rem 0 0; }
.meal-card .field-hint { margin: 0.1rem 0; }
.meal-card .week-menu__eaten { max-width: none; margin-top: 0.5rem; }
.meal-card__more { margin-top: 0.5rem; }
.meal-card__more > summary { cursor: pointer; color: var(--text-2); font-weight: 500; }
.meal-card__more[open] > summary { margin-bottom: 0.4rem; }
.meal-card__extra { display: grid; gap: 0.35rem; padding: 0.4rem 0; border-top: 1px solid var(--border); }
/* A dish's macros and what it contains, a line each, one tap away (#400). */
.meal-card__details { margin-top: 0.4rem; }
.meal-card__details > summary { cursor: pointer; color: var(--text-2); font-weight: 500; }
.meal-card__details[open] > summary { margin-bottom: 0.2rem; }
.meal-card__of { font-weight: 600; margin: 0.4rem 0 0; }

/* A dish as a food app shows one (#402, templates/_dish.html): its picture
   beside its name and figures, and its macros as chips. */
.meal-card__intro { display: flex; gap: 0.75rem; align-items: flex-start; }
.meal-card__about { flex: 1 1 auto; min-width: 0; container: dish / inline-size; }
/* Its Recipe under its name where beside it the name would be squeezed to
   less than 6.5rem - at 360px and below on a phone - and beside it where it
   is not, on the centre line of the name's first line, as everywhere
   (#372): the name starts as far down as half of what the 44px button is
   taller than its line. 13.75rem: 6.5rem, the gap, and Recipe's 101px. */
.meal-card__about .meal-card__head { flex-wrap: wrap; align-items: flex-start; }
.meal-card__about .meal-card__head > :first-child { flex: 1 1 100%; }
@container dish (min-width: 13.75rem) {
    .meal-card__about .meal-card__head { flex-wrap: nowrap; }
    .meal-card__about .meal-card__head > :first-child { flex: 1 1 auto; }
    .meal-card__about .meal-card__head:has(.meal-card__recipe) > :first-child { padding-top: calc((44px - 1lh) / 2); }
}
.dish-photo {
    flex: none;
    display: block;
    width: 40px;
    height: 40px;
    border-radius: var(--radius-sm);
    object-fit: cover;
    background: var(--surface-2);
}
.meal-card__macros + .meal-card__macros { margin-top: 0.4rem; }
.macro-chips { list-style: none; display: flex; flex-wrap: wrap; gap: 0.35rem; padding: 0; margin: 0.35rem 0; }
.macro-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.1rem 0.6rem;
    border-radius: var(--pill);
    font-size: 0.875rem;
    font-weight: 500;
    line-height: 1.6;
    background: var(--energy-tint);
    color: var(--accent);
}
/* A dot of its colour: what it is made of is in its words too. */
.macro-chip::before {
    content: '';
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background: currentColor;
}
.macro-chip--carb    { background: var(--carb-tint);    color: var(--carb-text); }
.macro-chip--carb::before    { background: var(--carb); }
.macro-chip--protein { background: var(--protein-tint); color: var(--protein-text); }
.macro-chip--protein::before { background: var(--protein); }
.macro-chip--fat     { background: var(--fat-tint);     color: var(--fat-text); }
.macro-chip--fat::before     { background: var(--fat); }
.macro-chip--fibre   { background: var(--fibre-tint);   color: var(--fibre-text); }
.macro-chip--fibre::before   { background: var(--fibre); }
.meal-cards__total { margin: 1rem 0 0; }
.meal-cards__total .macro-chips { margin-bottom: 0; }

/* ── Week: a dish's picture, and each day as cards on a phone (#402) ──
   A dish's picture sits at the start of its cell, before its name - it
   comes after it in the page, so that the cell's words come first. Below
   768px a day's table is laid out as a card a meal: its name, then each
   dish - its picture, name and what it contains - with its figures as chips
   under it, each naming its column, and the day's total last. It is still a
   table to a screen reader: _week_menu.html gives it its roles, which a
   change of display does not take away. From 768px up, the table. */
.week-menu__meals th.week-menu__dish { position: relative; padding-left: calc(0.75rem + 40px + 0.6rem); }
/* Its columns' names a little closer set than a table's, so that the
   picture costs the table no width it had: at 1280px it fits its card. */
.week-menu__meals > thead th { letter-spacing: 0.08em; }
.week-menu__meals th.week-menu__dish .dish-photo { position: absolute; left: 0.75rem; top: 0.65rem; width: 40px; height: 40px; }
@media (max-width: 767.98px) {
    .week-menu__day .week-menu__scroll-hint { display: none; }
    .week-menu__day .table-scroll:has(> .week-menu__meals) { overflow: visible; }
    .week-menu__meals,
    .week-menu__meals > tbody,
    .week-menu__meals > tfoot,
    .week-menu__meals tr,
    .week-menu__meals th,
    .week-menu__meals td { display: block; }
    .week-menu__meals { min-width: 0; }
    /* The columns' names, kept for a screen reader; each chip says its own. */
    .week-menu__meals > thead {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
    .week-menu__meals > tbody {
        border: 1px solid var(--border);
        border-radius: var(--radius-sm);
        background: var(--surface);
        padding: 0.6rem 0.9rem 0.75rem;
    }
    .week-menu__meals > tbody + tbody,
    .week-menu__meals > tfoot { margin-top: 0.75rem; }
    .week-menu__meals th,
    .week-menu__meals td { border: 0; padding: 0; }
    .week-menu__meals tr + tr { margin-top: 0.5rem; }
    .week-menu__meals th.week-menu__dish { padding-left: calc(40px + 0.6rem); min-height: 40px; }
    .week-menu__meals th.week-menu__dish .dish-photo { left: 0; top: 0; }
    /* A dish's row, and a total's: its name across, its figures as chips. */
    .week-menu__meals tr[data-dish],
    .week-menu__meals tr:has(> td.week-menu__figure) {
        display: flex;
        flex-wrap: wrap;
        gap: 0.35rem;
    }
    .week-menu__meals tr:has(> td.week-menu__figure) > th { flex: 1 1 100%; }
    .week-menu__meals td.week-menu__figure {
        display: inline-flex;
        align-items: center;
        gap: 0.3rem;
        padding: 0.1rem 0.6rem;
        border-radius: var(--pill);
        font-size: 0.875rem;
        line-height: 1.6;
        text-align: left;
        background: var(--surface-2);
    }
    .week-menu__meals td.week-menu__figure::before { font-weight: 600; }
    /* Its column's name, shown, and not read again after the heading it is. */
    .week-menu__figure--portion::before { content: "Portion"; content: "Portion" / ""; }
    .week-menu__figure--energy::before  { content: "Energy"; content: "Energy" / ""; }
    .week-menu__figure--carb::before    { content: "Carbohydrate"; content: "Carbohydrate" / ""; }
    .week-menu__figure--protein::before { content: "Protein"; content: "Protein" / ""; }
    .week-menu__figure--fat::before     { content: "Fat"; content: "Fat" / ""; }
    .week-menu__figure--fibre::before   { content: "Fibre"; content: "Fibre" / ""; }
    .week-menu__meals td.week-menu__figure--energy  { background: var(--energy-tint);  color: var(--accent); }
    .week-menu__meals td.week-menu__figure--carb    { background: var(--carb-tint);    color: var(--carb-text); }
    .week-menu__meals td.week-menu__figure--protein { background: var(--protein-tint); color: var(--protein-text); }
    .week-menu__meals td.week-menu__figure--fat     { background: var(--fat-tint);     color: var(--fat-text); }
    .week-menu__meals td.week-menu__figure--fibre   { background: var(--fibre-tint);   color: var(--fibre-text); }
    .week-menu__meals > tfoot { padding: 0.6rem 0.9rem; border-radius: var(--radius-sm); background: var(--surface-2); }
    .week-menu__meals > tfoot td.week-menu__figure { background: var(--surface); }
}

/* The day so far (#402): energy eaten against the target, a bar under its
   words, and each macro against the menu's plan, side by side where they
   fit. A bar is a <meter>, drawn from its value: its track, and its fill. */
.day-progress {
    margin: 0.5rem 0 0;
    padding: 0.4rem 0.9rem 0.5rem;
    border-radius: var(--radius-sm);
    background: var(--surface-2);
}
/* The energy's words, and its bar under them: always there, so that no
   font arriving - the page's, after a fallback - moves it from beside them
   to under them, and the words wrap as long as they are. */
.day-progress__top { display: grid; gap: 0.25rem; }
.day-progress__energy { min-width: 0; margin: 0; line-height: 1.4; }
.day-progress__energy strong { margin-right: 0.15rem; }
.day-progress__over { color: var(--warning-text); font-weight: 600; }
.day-progress__title { margin: 0 0 0.3rem; font-size: 0.875rem; font-weight: 600; color: var(--text-2); }
.day-progress__uncounted { margin: 0.4rem 0 0; }
.day-progress__energy strong { font-family: var(--font-head); font-size: 1.3rem; line-height: 1; color: var(--accent); }
.day-progress meter {
    display: block;
    width: 100%;
    height: 0.5rem;
    -webkit-appearance: none;
    appearance: none;
    border: 0;
    border-radius: var(--pill);
    background: var(--track);
    overflow: hidden;
}
.day-progress meter::-webkit-meter-bar { height: 0.5rem; border: 0; border-radius: var(--pill); background: var(--track); }
.day-progress meter::-webkit-meter-optimum-value,
.day-progress meter::-webkit-meter-suboptimum-value,
.day-progress meter::-webkit-meter-even-less-good-value { border-radius: var(--pill); background: var(--fill, var(--accent-fill)); }
.day-progress meter::-moz-meter-bar { border-radius: var(--pill); background: var(--fill, var(--accent-fill)); }
.day-progress__meter.is-over { --fill: var(--warning-fill); }
.day-progress__macros {
    list-style: none;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0.5rem 0.75rem;
    padding: 0;
    margin: 0.5rem 0 0;
}
.day-progress__macro { font-size: 0.875rem; line-height: 1.3; }
.day-progress__macro meter { height: 0.375rem; margin-top: 0.2rem; }
.day-progress__name { display: block; font-weight: 600; }
.day-progress__macro--carb    { --fill: var(--carb); }
.day-progress__macro--carb .day-progress__name    { color: var(--carb-text); }
.day-progress__macro--protein { --fill: var(--protein); }
.day-progress__macro--protein .day-progress__name { color: var(--protein-text); }
.day-progress__macro--fat     { --fill: var(--fat); }
.day-progress__macro--fat .day-progress__name     { color: var(--fat-text); }

/* Today in the order of the athlete's day (#400): Now, then Later today,
   each under a small heading, and Earlier today one row that opens. */
.today-meals__part + .today-meals__part { margin-top: 1rem; }
.today-meals__heading { font-size: 0.95rem; font-weight: 600; color: var(--text-2); margin: 1rem 0 0; }
/* Today's card a little tighter under its date (#402): its day's line, the
   day so far and the meal due come first on a phone. */
.card--today > .card-section { padding-top: 0.75rem; }
.card--today > .card-header { padding-top: 1rem; padding-bottom: 0.75rem; }
/* 44px tall, as every control. */
.card--today .week-menu__day > summary { padding: 0.6rem 0; }
.card--today .meal-card { padding-top: 0.6rem; }
/* Close under the day so far, which is the top of the day. */
.day-progress + .today-meals > .today-meals__part:first-child > .today-meals__heading { margin-top: 0.6rem; }
.today-meals__heading + .meal-cards,
.today-meals__heading + .field-hint { margin-top: 0.5rem; }
.today-meals__earlier { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 0 0.9rem; background: var(--surface); }
/* Its triangle kept, as every fold's: a list item, 44px tall by its padding. */
.today-meals__earlier > summary { display: list-item; padding: 10px 0; line-height: 24px; cursor: pointer; font-weight: 600; }
.today-meals__earlier > .meal-cards { margin: 0 0 0.9rem; }
.meal-card__extra details > summary { cursor: pointer; }

/* How was it? Five stars, a radio each in its label: those up to the one
   chosen filled; the focused one ringed. */
.meal-card__stars .group { display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem 0.6rem; border: 0; padding: 0; margin: 0.4rem 0 0; }
.meal-card__stars legend { float: left; display: flex; align-items: center; min-height: 44px; margin-right: 0.4rem; font-weight: 500; }
.stars { display: inline-flex; gap: 0.1rem; }
.stars label {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 2.25rem;
    min-height: 44px;
    font-size: 1.4rem;
    line-height: 1;
    /* An empty star its outline, as dark as hint text against the card. */
    color: transparent;
    -webkit-text-stroke: 1.5px var(--text-2);
    cursor: pointer;
    border-radius: var(--pill);
}
.stars label:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 1px; }
.stars:has(input[value="1"]:checked) label:nth-child(-n+1),
.stars:has(input[value="2"]:checked) label:nth-child(-n+2),
.stars:has(input[value="3"]:checked) label:nth-child(-n+3),
.stars:has(input[value="4"]:checked) label:nth-child(-n+4),
.stars:has(input[value="5"]:checked) label:nth-child(-n+5) {
    color: var(--yellow-text);
    -webkit-text-stroke-color: var(--yellow-text);
}

/* What the pantry holds to use soon, at the top of Today. */
.meals-use-soon a { font-weight: 600; }
/* A line, not a card, above the meals (#402): the meal due comes first. */
.meals-use-soon { padding-block: 0.5rem; margin-bottom: 0; }

/* What goes off with no meal using it (#356, ADR-0115): on the alternate
   ground, as a notice is, under its heading a line an item - its words,
   then the meal to ask anew or the Pantry tab, under them where the two do
   not fit on a line - and the hint after them. On Today, after the day. */
.going-off { padding: 0.9rem 1.2rem; border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface-2); color: var(--text-2); font-size: 0.95rem; }
.today-meals__part + .going-off { margin-top: 1rem; margin-bottom: 0; }
.going-off__heading { font-size: 1rem; font-weight: 600; color: var(--text); margin: 0 0 0.5rem; }
.going-off__items { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.5rem; }
.going-off__item { display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem 0.75rem; overflow-wrap: anywhere; }
.going-off__ask { margin: 0; }
/* A label on two lines, on a phone, as tall as a button of the full size:
   one of the two heights of button, as on one line it is 44px. */
.going-off__ask .btn { padding-block: 0.5rem; }
.going-off__pantry { display: inline-flex; align-items: center; min-height: 44px; font-weight: 600; }
.going-off__more,
.going-off__hint { margin: 0.5rem 0 0; }

/* What no day the athlete shops keeps until its dish (#357), on its shop
   of a day not shopped on: on the alternate ground, as what goes off is,
   under a line in bold a line an item - its words, then each meal to ask
   anew, under them where they do not fit on a line - and the hint after
   them. Its forms are what goes off's (.going-off__ask). */
.not-kept { padding: 0.9rem 1.2rem; border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface-2); color: var(--text-2); font-size: 0.95rem; }
.not-kept__heading { color: var(--text); margin: 0 0 0.5rem; }
.not-kept__items { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.5rem; }
.not-kept__item { display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem 0.75rem; overflow-wrap: anywhere; }
.not-kept__hint { margin: 0.5rem 0 0; }

/* What a Pantry page form did, shown while the address names it. */
.pantry__said { display: none; }
.pantry__said:target { display: block; }
.pantry__items { list-style: none; margin: 0.5rem 0 1rem; padding: 0; display: grid; gap: 0.5rem; }
/* A row (#363): what it is, how much and its date, read-only; Edit opens
   its own form beneath. */
.pantry__item { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 0.4rem 0.75rem; }
.pantry__item:target { outline: 2px solid var(--accent); outline-offset: 2px; }
.pantry__row { display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem 0.75rem; margin: 0; overflow-wrap: anywhere; }
.pantry__edit > summary { min-height: 44px; display: flex; align-items: center; cursor: pointer; color: var(--text-2); font-weight: 500; }
.pantry__item summary:focus-visible,
.hint-help > summary:focus-visible,
.pantry__used > summary:focus-visible,
.pantry__taken > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.pantry__edit[open] > summary { margin-bottom: 0.25rem; }
.pantry__actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.75rem; margin: 0.5rem 0 0.25rem; }
.pantry__group { font-size: 1rem; margin: 0.75rem 0 0; }
.pantry__used > summary { cursor: pointer; }
/* What meals took, a fold at the foot as the others are (#372): its summary
   at the card's padding, in the colour of a link, as wide as its words. */
.pantry__taken > summary {
    cursor: pointer;
    width: fit-content;
    margin: 1.25rem 1.75rem;
    font-weight: 600;
    color: var(--accent);
}
.pantry__taken[open] > summary { margin-bottom: 0; }
.hint-help { display: inline-block; }
.hint-help[open] { display: block; }
.hint-help > summary {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border: 1px solid var(--border-bright);
    border-radius: var(--pill);
    cursor: pointer;
    list-style: none;
    font-weight: 600;
}
.hint-help > summary::-webkit-details-marker { display: none; }
.pantry__add > summary { list-style: none; }
.pantry__add > summary::-webkit-details-marker { display: none; }
.pantry__add-form { padding: 0.5rem 0 0.25rem; }
/* + Add's More, a line 44px tall to tap, as every other control is. */
.pantry__more > summary { padding: 10px 0; line-height: 24px; cursor: pointer; }

/* A row of buttons, a "?" among them (#372): an .inline-fold's summary sits
   in the row with the rest, and what it opens takes a line of its own under
   the whole row - the <details> no box of its own, its summary and its
   content (::details-content) the row's own items. A browser without
   ::details-content keeps the fold whole, a line of its own once open. */
.button-row { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.75rem; }
.button-row > .week-menu__notes { margin: 0; }
.inline-fold[open] { flex: 1 0 100%; }
@supports selector(::details-content) {
    .inline-fold,
    .inline-fold[open] { display: contents; }
    /* Open only: a shut fold's content keeps a box, which would add a line. */
    .inline-fold[open]::details-content { flex: 1 0 100%; order: 1; }
}
.hint-help__said > * + * { margin-top: 0.5rem; }

/* A fold on a line of its own (#377): its summary a target, and says so. */
.fold > summary { cursor: pointer; min-block-size: 24px; }

/* What was agreed to, behind "More" once it is (#448): a link's look and no
   marker, "Less" while it is open. */
.more-wording > summary {
    display: inline-flex;
    align-items: center;
    list-style: none;
    color: var(--link);
    font-size: 0.875rem;
    text-decoration: underline;
    text-underline-offset: 0.18em;
}
.more-wording > summary::-webkit-details-marker { display: none; }
.more-wording > summary:hover { color: var(--link-hover); }
.more-wording > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.more-wording[open] .more-wording__more,
.more-wording:not([open]) .more-wording__less { display: none; }

/* A pantry row (#363): what it is, how much and its date, with its Edit at
   the end of the line, and Edit's form under it (#372). */
.pantry__item { display: flex; flex-wrap: wrap; align-items: center; gap: 0 0.75rem; }
.pantry__item > .pantry__row { flex: 1 1 14rem; min-width: 0; }
.pantry__name { font-weight: 600; }

/* The Pantry's Sport tab (#511): a line of links to its lists, then each
   list a card of rows as the Pantry's food has them - a name that wraps
   anywhere, its figures and its count wrapping under it at 320px, never a
   table that scrolls - and the row a jump or a save lands on outlined. */
.sport__jump { margin: 0 0 1rem; overflow-wrap: anywhere; }
.sport__split:target { outline: 2px solid var(--accent); outline-offset: 2px; }
.sport__list { list-style: none; margin: 0.5rem 0 0; padding: 0; display: grid; gap: 0.5rem; }
.sport__item { display: flex; flex-direction: column; gap: 0.1rem; border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 0.4rem 0.75rem; overflow-wrap: anywhere; }
.sport__item:target { outline: 2px solid var(--accent); outline-offset: 2px; }
.sport__row { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.1rem 0.75rem; margin: 0; min-width: 0; }
.sport__name { font-weight: 600; min-width: 0; }
.sport__stock { color: var(--text-2); }
.sport__group { font-size: 1rem; margin: 1rem 0 0.25rem; }
/* What was taken during a session (#514): its page's plan folded under it,
   the summary a full-height line to tap; and each Sport line of what came
   off, its Change beside it. */
.taken-fold > summary { padding: 10px 0; line-height: 24px; cursor: pointer; font-weight: 600; }
.taken-fold > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* A card's Edit form and + Add's (the owner's report on #531's cards): its
   columns come from the form's own width, not the screen's - a card on a
   wide screen is about as wide as a phone. One field a line; two where
   both labels fit on a line and a date box shows its whole date, the
   fields of a pair side by side, and what is wide - a hint, Category,
   What - across both. A label breaks only between words, and every box
   fills its column, never wider: a date box keeps a width of its own
   otherwise. A drop-down's text runs to its arrow's room, so that the
   longest category shows whole in a card on a 320px phone. The card is the
   container, not its form: a grid item's width is the grid's, where a
   form in a fold of a flex card would be laid out at no width at all
   before it is stretched, and as tall as that makes it. */
.pantry__items > .pantry-card,
.pantry__add-form { container: pantry-form / inline-size; }
.pantry-card .pantry__form { overflow-wrap: break-word; }
.pantry__fields { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0.75rem 1rem; margin: 0.5rem 0; }
@container pantry-form (min-width: 24rem) {
    .pantry__fields { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .pantry__fields > .pantry__wide { grid-column: 1 / -1; }
}
.pantry__fields .field { min-width: 0; }
.pantry__fields .form-error,
.pantry__fields p.field-hint { margin-block: 0; }
/* A hint about the field above it, as close to it as a field's own. */
.pantry__fields > p.field-hint { margin-top: -0.3rem; }
/* The Food cards a little wider than the other lists' 15rem: wide enough
   for the longest category, "Meat, fish and alternatives", whole in its
   drop-down beside its arrow, so that a card opened to edit keeps its place
   and its width. One that took two columns for that room moved, the last
   of a row, to the start of the next, away from the Edit just tapped; and a
   browser without container queries gave it no room at all. */
.pantry-cards.pantry__items { grid-template-columns: repeat(auto-fill, minmax(min(100%, 17.5rem), 1fr)); }
.pantry__fields input,
.pantry__fields select { width: 100%; max-width: 100%; min-width: 0; box-sizing: border-box; }
.pantry__fields select { padding-right: 2.25rem; }
/* Inline, so that its words wrap in the summary like any others. */
.pantry__state { display: inline; font-weight: 600; }
.pantry__state .pantry__icon,
.pantry-key .pantry__icon { vertical-align: -0.2em; margin-right: 0.3rem; }
.pantry__state--soon { color: var(--yellow-text); }
.pantry__state--past { color: var(--red-text); }
.pantry__amount--used { color: var(--muted); }
.pantry__icon { width: 1.1em; height: 1.1em; flex: none; }
/* The issue's yellow and red: a filled shape whose lines are at 3:1 or
   more against it and the page - never the yellow's 1.75:1 alone. On the
   icon itself, so that the key under the heading draws them as a card
   does. */
.pantry__icon--soon .pantry__icon-shape { fill: var(--light-yellow); stroke: var(--yellow-text); }
.pantry__icon--soon .pantry__icon-mark { stroke: var(--on-light-yellow); }
.pantry__icon--past .pantry__icon-shape { fill: var(--red-text); stroke: var(--red-text); }
.pantry__icon--past .pantry__icon-mark { stroke: var(--surface); }
.pantry__uses { list-style: none; padding: 0; margin: 0.5rem 0 0; display: grid; gap: 1rem; }
.pantry__uses > li > p { margin: 0 0 0.25rem; }

/* ── A shopping list, one at a time (#373) ─────────────── */
/* The week's list by aisle, or a day's: its title, then each aisle a fold
   with its name, how many products and how many are not bought yet - the
   warning in an icon and words, never colour alone - open while something
   is left to buy. */
.week-menu__list-title { font-size: 1.05rem; margin: 0; }
.week-menu__list-day { font-size: 1rem; margin: 0; }
.shopping-aisles { display: grid; gap: 0.5rem; }
.shopping-aisle { border: 1px solid var(--border); border-radius: var(--radius-sm); }
.shopping-aisle > summary {
    cursor: pointer;
    min-height: 44px;
    padding: 0.6rem 0.75rem;
    box-sizing: border-box;
    font-weight: 600;
    color: var(--text);
    overflow-wrap: anywhere;
}
.shopping-aisle > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.shopping-aisle__count,
.shopping-aisle__done { font-weight: 400; color: var(--text-2); }
.shopping-aisle__left { color: var(--yellow-text); }
.shopping-aisle__done .shopping-aisle__icon { color: var(--green-text); }
.shopping-aisle__icon { display: inline; width: 1.1em; height: 1.1em; vertical-align: -0.2em; margin-right: 0.25rem; }
.shopping-aisle__icon--left .shopping-aisle__icon-shape { fill: var(--light-yellow); stroke: var(--yellow-text); }
.shopping-aisle__icon--left .shopping-aisle__icon-mark { stroke: var(--yellow-text); }
.shopping-aisle__body { border-top: 1px solid var(--border); padding: 0.75rem; }
/* The aisle's own Notes button heads its products, at their right edge. */
.shopping-aisle__body > .week-menu__notes-cell { display: flex; justify-content: flex-end; margin-bottom: 0.5rem; }
.shopping-aisle__body > .buy-cards { margin: 0; }
/* Each part of an aisle's summary wraps as a whole, its icon with its words. */
.shopping-aisle__count,
.shopping-aisle__left,
.shopping-aisle__done { white-space: nowrap; }

/* ── The dashboard's day (#374, ADR-0083) ─────────────────── */
/* The next meal, today's training and the weigh-in: one column on a phone,
   side by side where they fit, each card its own height. With the next meal
   (#402), it is the wide column and the other two stand beside it, one over
   the other: a dish has its picture, its name and its Recipe on one line. */
.dashboard-today { display: grid; gap: 0 1.5rem; align-items: start; }
@media (min-width: 64rem) {
    .dashboard-today { grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); }
    /* The next meal's height its own row's: the weigh-in under the training. */
    .dashboard-today:has(> #next-meal) { grid-template-columns: minmax(0, 2fr) minmax(18rem, 1fr); grid-template-rows: auto 1fr; row-gap: 1.5rem; }
    .dashboard-today > #next-meal { grid-row: span 2; }
}
.next-meal__dishes { list-style: none; display: grid; gap: 0.5rem; padding: 0; margin: 0; }
.next-meal__dishes > li { min-width: 0; }

/* Plan my session's timing (#512): when, then what to take, a row each - a
   list, not a table, so that nothing scrolls sideways at 320px. */
.intake-list { list-style: none; display: grid; gap: 0.5rem; padding: 0; margin: 0; }
.intake-list > li { display: flex; flex-wrap: wrap; gap: 0 0.75rem; min-width: 0; overflow-wrap: anywhere; }
/* What the target's form just did, said at the top of the plan it lands on. */
#planSaid.is-visible { margin-bottom: 1rem; }
.intake-list__time { flex: none; min-width: 6.5rem; font-variant-numeric: tabular-nums; font-weight: 600; }
/* The dishes set off from what is above them: the day so far, or a day's
   snacks' Mark as eaten (ADR-0151). */
.day-progress + .next-meal__dishes,
.button-row + .next-meal__dishes,
.day-progress + .field-hint { margin-top: 0.9rem; }

/* The energy status (#424, ADR-0105): first on the dashboard. Its state is a
   word on its role's tint - never colour alone - beside the title, and the
   card's edge is the role's fill. Rendered with the page: nothing moves in. */
.energy {
    border-left: 0.35rem solid var(--border);
    padding: 1.25rem 1.75rem;
}
.energy__head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.35rem 1rem;
}
.energy__title {
    margin: 0;
    font-family: var(--font-head);
    font-weight: 600;
    font-size: clamp(20px, 2.2vw, 25px);
    letter-spacing: 0.014em;
    line-height: 1.08;
    color: var(--accent);
}
.energy__state {
    margin: 0;
    padding: 0.15rem 0.7rem;
    border-radius: 999px;
    border: 1px solid transparent;
    font-family: var(--font-head);
    font-weight: 600;
}
.energy__said { margin: 0.6rem 0 0; }
.energy__hint { margin: 0.5rem 0 0; }
.energy__why { margin: 0.25rem 0 0; }
.energy[data-state="fuelled"] { border-left-color: var(--success-fill); }
.energy[data-state="fuelled"] .energy__state { background: var(--success-tint); color: var(--success-text); }
.energy[data-state="act-now"] { border-left-color: var(--light-yellow); }
.energy[data-state="act-now"] .energy__state { background: var(--warning-tint); color: var(--warning-text); }
.energy[data-state="short"] { border-left-color: var(--light-red); }
.energy[data-state="short"] .energy__state { background: var(--danger-tint); color: var(--red-text); }
.energy[data-state="unmarked"] .energy__state { border-color: var(--border-bright); color: var(--muted); }
@media (max-width: 30rem) {
    .energy { padding: 1rem 1.1rem; }
    /* The state under the title on a phone: beside it, it fits in one face
       and not in another, and the card would change height as the page's
       own arrived (#424). */
    .energy__head { flex-direction: column; align-items: flex-start; }
}
/* A feature not here yet, said beside its name. */
.badge {
    display: inline-block;
    padding: 0 0.45rem;
    border: 1px solid currentColor;
    border-radius: var(--pill);
    font-size: 0.875rem;
    font-weight: 600;
    line-height: 1.5;
    vertical-align: middle;
}
.badge--soon { color: var(--text-2); }

/* ── Get set up (#375, ADR-0084) ──────────────────────────── */
/* An ordered list whose numbers are its order: each step's title and its
   state in words on one line, what to do under them, the link last. */
.setup__count { margin: 0 0 1rem; color: var(--text-2); font-weight: 600; }
/* An optional choice's state, quieter than a step's. */
.badge--choice { color: var(--text-2); }
.setup-list { display: grid; gap: 1rem; margin: 0; padding-left: 1.5rem; }
.setup-step { min-width: 0; }
.setup-step__head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 0.75rem; }
.setup-step__title { font-weight: 600; }
.setup-step__detail { margin: 0.25rem 0 0; }
.setup-step__choices { list-style: none; display: flex; flex-wrap: wrap; gap: 0.25rem 1rem; margin: 0.35rem 0 0; padding: 0; font-size: 0.9rem; }
.setup-step__link { display: inline-block; margin-top: 0.35rem; font-weight: 600; color: var(--accent); }
.setup-step[data-state="done"] .setup-step__state { color: var(--green-text); }
.setup-step[data-state="to-do"] .setup-step__state { color: var(--rock-text); }
.setup-step[data-state="optional"] .setup-step__state { color: var(--text-2); }
.setup__all-done { margin: 0 0 1rem; }

/* Where the athlete shops, at the top of the Shopping tab (#381): the place
   and its Change on one row, the form under the row once open. */
.shopping-for__said { margin: 0; }
.shopping-for__form { padding-top: 0.5rem; }
/* The days the athlete shops, under where they shop (#357): the line and
   its Change on one row as where they shop's are, the form under the row
   once open, the seven days in as many columns as hold a day's name - one
   at 320px - each box a row its name makes 44px tall to press. */
.shopping-days__said { margin: 0; }
.shopping-days__form { padding-top: 0.5rem; }
.shopping-days__days { border: 0; margin: 0; padding: 0; min-inline-size: 0; }
.shopping-days__days > legend { padding: 0; margin-bottom: 0.25rem; font-weight: 600; color: var(--text); }
.shopping-days__grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(8.5rem, 100%), 1fr)); gap: 0 1rem; }
.shopping-days__day { align-items: center; min-height: 44px; }
.shopping-days__day input[type="checkbox"] { margin: 0; }
/* Use up fresh food first (#359): its line as When you shop's, and its one
   box a row as tall as a finger, its words beside it. */
.fresh-first__said { margin: 0; }
.fresh-first__form { padding-top: 0.5rem; }
.fresh-first__box { align-items: center; min-height: 44px; }
.fresh-first__box input[type="checkbox"] { margin: 0; }
/* How the athlete buys, under where they shop (#356): the line and its
   Change on one row as where they shop's are, the forms under the row once
   open, each product set on its own a line of its words and its Remove. */
.buying__said { margin: 0; }
.buying__forms { padding-top: 0.5rem; }
.buying__form + .buying__form { margin-top: 1.25rem; }
.buying__product { border: 0; margin: 0; padding: 0; min-width: 0; }
.buying__product > legend { padding: 0; margin-bottom: 0.5rem; font-weight: 600; color: var(--text); }
.buying__own { list-style: none; padding: 0; display: grid; gap: 0.5rem; }
.buying__own-item { display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem 0.75rem; overflow-wrap: anywhere; }
.buying__remove { margin: 0; }
/* The suggestions of how to buy, under How you buy (#358): each question a
   line of its own, its two answers under it in one row that wraps, each
   44px tall to press - and a hairline between one question and the next,
   as between a card's sections, so that each reads with its own buttons
   where they wrap on a phone. */
.buying-suggestions__list { list-style: none; padding: 0; margin-bottom: 0; display: grid; gap: 1rem; }
.buying-suggestion + .buying-suggestion { border-top: 1px solid var(--border); padding-top: 1rem; }
.buying-suggestion__form { margin: 0; }
.buying-suggestion__said { margin: 0; overflow-wrap: anywhere; }
.buying-suggestion__answers { display: flex; flex-wrap: wrap; gap: 0.5rem 0.75rem; margin-top: 0.5rem; }
/* A card the athlete says how they buy (#356): the link to How you buy,
   under what it says - above its card's own link (#579). */
.week-menu__buy { display: block; width: fit-content; min-height: 44px; padding-block: 0.6rem; font-size: 0.875rem; font-weight: 600; color: var(--accent); }
.week-menu__buy:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* What the menus are planned with, in short, on Nutrition (#381). */
.menus-summary { display: grid; gap: 0.75rem; margin: 0; }
.menus-summary dt { font-weight: 600; color: var(--text); }
.menus-summary dd { margin: 0; }
/* A rating taken back on its dish (#381), under the form that gives it. */
.week-menu__unrate { margin-top: 0.25rem; }

/* A form after a section of its card, its sections inside it: divided from
   what is above as a section after a section is (#382); and after a fold's
   summary drawn as a section, the export's (#386). */
.card-section + form > .card-section:first-child { border-top: 1px solid var(--border); }
.fold > summary.card-section { color: var(--text-2); }

/* A menu on its way, or ready, on every page (#396, _menu_watch.html): no
   box of its own, so that its card, and the noscript one, are the stack's
   (#532). */
.menu-watch { display: contents; }

/* A card's header with something beside its title - a lamp, a link -
   which is the header's, not the heading's (#382). */
.card-header__title { font: inherit; letter-spacing: inherit; color: inherit; }


/* What is held, as deleting the account and the archive say it (#382). */
.held-list { padding-left: 1.2rem; color: var(--muted); font-size: 0.875rem; }
.held-list > li + li { margin-top: 0.2rem; }

/* ── Forced colours (#384) ─────────────────────────────── */
/* In Windows' high-contrast themes every fill is the system's own, so a
   chip, a segment, a tab or a link chosen only by its fill looks like every
   other. Each chosen state is bordered in the system's Highlight instead -
   a border, so that the focus ring, an outline, is still seen on it - and
   the stars are drawn in system colours, a given one filled with Highlight
   (WCAG 1.4.11). tests/test_accessibility_sweeps.py holds that every chosen
   element here is bordered; the stars are tests/test_layout.py's. */
@media (forced-colors: active) {
    .chip:has(input:checked),
    .segmented label:has(input:checked),
    .meals-switch__week[aria-current="page"],
    .pantry-categories__link[aria-current="page"],
    .page-tabs__tab[aria-current="page"],
    .week-days__day[aria-current="page"],
    .navbar-nav a[aria-current],
    .navbar-quick[aria-current],
    .mobile-nav a[aria-current],
    .u-dropdown a[aria-current],
    .btn-ghost.is-active {
        border: 2px solid Highlight;
    }

    .stars label {
        forced-color-adjust: none;
        color: Canvas;
        -webkit-text-stroke-color: CanvasText;
    }

    .stars:has(input[value="1"]:checked) label:nth-child(-n+1),
    .stars:has(input[value="2"]:checked) label:nth-child(-n+2),
    .stars:has(input[value="3"]:checked) label:nth-child(-n+3),
    .stars:has(input[value="4"]:checked) label:nth-child(-n+4),
    .stars:has(input[value="5"]:checked) label:nth-child(-n+5) {
        color: Highlight;
        -webkit-text-stroke-color: Highlight;
    }

    /* A macro's chip and the day so far's bars (#402): their tints are
       gone, so a chip is a bordered word and a bar its outline. */
    .macro-chip { border: 1px solid CanvasText; }
    .macro-chip::before { background: CanvasText; }
    .day-progress meter { border: 1px solid CanvasText; }
    @media (max-width: 767.98px) {
        .week-menu__meals td.week-menu__figure { border: 1px solid CanvasText; }
    }

    /* A drop-down's arrow is two gradients (#277), which forced colours
       take away, leaving a box like any text box: the browser's own
       drop-down here, its arrow in the system's colours. */
    .field select { appearance: auto; -webkit-appearance: auto; }
}

/* ── Recipes (#347, ADR-0117) ─────────────────────────────── */
/* The athlete's own recipes, told apart from those of their menus' dishes by
   the Yours badge (_yours_badge.html): a word and a mark, on the warm accent's
   tint in either scheme - never its colour alone. */
.badge--yours {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    border-color: var(--rock-text);
    background: var(--rock-tint);
    color: var(--on-rock-tint);
    white-space: nowrap;
}
.badge__icon { flex: none; }
.recipes__list { list-style: none; margin: 0.5rem 0 1rem; padding: 0; display: grid; gap: 0.5rem; }
.recipes__item {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.25rem 0.75rem;
    min-height: 44px;
    padding: 0.4rem 0.75rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    overflow-wrap: anywhere;
}
.recipes__head { display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem 0.5rem; min-width: 0; }
.recipes__name { font-weight: 600; min-width: 0; }
.recipes__facts { flex: 1 1 100%; }
.recipes__clear > summary { cursor: pointer; width: fit-content; min-height: 44px; display: flex; align-items: center; color: var(--missed-text); font-weight: 600; }
.recipes__clear > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* A recipe's own page: every word the file's, wrapped where it has to be. */
.recipe__back { margin: 0 0 0.5rem; }
.recipe__title { overflow-wrap: anywhere; }
.recipe__title .badge { font-family: var(--font-body); }
.recipe { overflow-wrap: anywhere; }
.recipe__said { margin: 0 0 1rem; }
.recipe__facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 10rem), 1fr)); gap: 0.5rem 1rem; margin: 0 0 1rem; }
.recipe__fact dt { font-size: 0.875rem; color: var(--muted); }
.recipe__fact dd { margin: 0; }
.recipe__heading { font-size: 1.05rem; margin: 1rem 0 0.35rem; }
.recipe__lines { margin: 0 0 0.5rem; padding-left: 1.25rem; }
.recipe__source { margin: 0 0 0.25rem; }
/* Where a recipe stands on the tab, and the way to match it (#508). */
.recipes__standing { flex: 1 1 100%; }
/* Our steps for a dish (#567, ADR-0132): under its name, numbered, each a
   line of its own, wrapped where it has to be; then who wrote them. */
.recipes__item--steps { display: block; }
.recipes__steps { margin: 0.35rem 0 0.5rem; padding-left: 1.5rem; }
.recipes__steps li { overflow-wrap: anywhere; margin: 0 0 0.25rem; }
.recipes__item--steps .field-hint { margin: 0 0 0.25rem; }
/* A recipe's categories (#580, ADR-0139): words on tinted pills under its
   name, wrapping, as a meal's macro chips are (.macro-chip) - a ground and
   no edge, so that none is taken for the side's bordered links on a phone;
   the border is there, transparent, for forced colours to draw. Where they
   come from is said once above the lists. */
.recipes__filed { margin: 0 0 1rem; }
.recipes__categories { list-style: none; display: flex; flex-wrap: wrap; gap: 0.35rem; margin: 0; padding: 0; }
.recipes__category {
    padding: 0 0.6rem;
    border: 1px solid transparent;
    border-radius: var(--pill);
    background: var(--energy-tint);
    color: var(--accent);
    font-size: 0.875rem;
    font-weight: 500;
    line-height: 1.6;
}
.recipe__match { margin: 0.5rem 0 0; }

/* Matching a recipe to our ingredients (#508, ADR-0124): a block a line, the
   athlete's words over What at full width, then Grams and Leave this out on
   one row while they fit, under each other on the narrowest phone. */
.match__all { border: 0; margin: 0; padding: 0; min-inline-size: 0; }
.match__standing { margin: 0 0 0.5rem; }
.match__slots { border: 0; margin: 0; padding: 0; min-inline-size: 0; }
.match__slots > legend { padding: 0; margin-bottom: 0.25rem; font-weight: 600; color: var(--text); }
.match__slot-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(8.5rem, 100%), 1fr)); gap: 0 1rem; }
.match__slot { align-items: center; min-height: 44px; }
.match__slot input[type="checkbox"] { margin: 0; }
.match__minutes { max-width: 16rem; }
.match__lines { list-style: none; margin: 0.75rem 0 0; padding: 0; display: grid; gap: 0.75rem; counter-reset: line; }
.match__line {
    overflow-wrap: anywhere;
    padding: 0.6rem 0.75rem 0.75rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    min-width: 0;
}
.match__line-set { border: 0; margin: 0; padding: 0; min-inline-size: 0; display: grid; gap: 0.5rem; }
.match__text { padding: 0; font-weight: 600; color: var(--text); overflow-wrap: anywhere; max-width: 100%; }
.match__tag {
    display: inline-block;
    width: fit-content;
    padding: 0 0.4rem;
    border: 1px solid var(--border-bright);
    border-radius: var(--radius-sm);
    background: var(--surface-3);
    color: var(--text);
    font-size: 0.875rem;
}
.match__amount { display: flex; flex-wrap: wrap; align-items: end; gap: 0.5rem 1rem; }
.match__grams { flex: 1 1 10rem; min-width: 0; }
.match__out { align-items: center; min-height: 44px; }
.match__out input[type="checkbox"] { margin: 0; }
.match__sum { margin: 0 0 0.35rem; overflow-wrap: anywhere; }
.match__summary ul { margin: 0.35rem 0 0; padding-left: 1.25rem; }
.match__fold > summary { cursor: pointer; min-height: 44px; display: flex; align-items: center; }
.match__fold > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.match__ours { margin: 0 0 0.75rem; padding-left: 1.25rem; overflow-wrap: anywhere; }
.match__name { font-weight: 600; }

/* Five Meals tabs on a phone's bar, with Recipes (#347): each column as wide
   as its word at least, and what is left shared, so that "Shopping" fits in
   320px at the bar's size. */
@media (max-width: 768px) {
    .page-tabs--meals.page-tabs--count-5 .page-tabs__list {
        grid-template-columns: repeat(5, minmax(min-content, 1fr));
    }
}

/* A meal not on the menu, logged by hand (#425): the meals logged and saved,
   each its name and its Undo or Log this on a row while they fit; and the
   form's rows as the matching page's, with a food's own figures in a fold,
   two to a row on a phone. */
.log__meals { list-style: none; margin: 0.5rem 0 0; padding: 0; display: grid; gap: 0.75rem; }
.log__meal { overflow-wrap: anywhere; min-width: 0; }
.log__meal-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.25rem 0.75rem; }
.log__meal-name { margin: 0; font-weight: 600; min-width: 0; }
.log__head { display: grid; gap: 0.75rem; grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr)); }
.log__own > summary { cursor: pointer; min-height: 44px; display: flex; align-items: center; }
.log__own > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.log__per { border: 0; margin: 0; padding: 0; min-inline-size: 0; }
.log__per-grid { display: grid; gap: 0.5rem 0.75rem; grid-template-columns: repeat(auto-fill, minmax(min(9rem, 100%), 1fr)); }
/* A food said in a sentence that names a few of ours (#425): one radio
   button each, under the words that named them. */
.log__choices { border: 0; margin: 0 0 0.5rem; padding: 0; min-inline-size: 0; display: grid; gap: 0.25rem; overflow-wrap: anywhere; }
.log__choices > legend { padding: 0; margin-bottom: 0.25rem; max-width: 100%; }
/* A photo kept with a meal (#425, ADR-0131): a fixed square beside its
   name, cropped to fill it, so that no photo moves the row it is on. */
.log__pictured { display: flex; align-items: center; gap: 0.75rem; min-width: 0; }
.log__photo { flex: none; width: 4rem; height: 4rem; object-fit: cover; border-radius: 0.5rem; background: var(--surface-2); }
/* Where the day's meals are (#425): what was logged, a row each with its
   badge and Undo, and the saved meals' one-tap buttons wrapping as they must. */
.log-it { display: grid; gap: 0.5rem; }
.log-it__total { margin: 0; }
.log-it__meals { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.25rem; }
.log-it__meal { display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem 0.5rem; min-width: 0; overflow-wrap: anywhere; }
.log-it__name { min-width: 0; flex: 1 1 10rem; }
.log-it .btn { max-width: 100%; white-space: normal; overflow-wrap: anywhere; }

/* ── The Pantry page (#531, ADR-0137) ───────────────────── */
/* The unified control bar: the search, the quick filters and Sort by on one
   row of a wide screen, one under the other on a narrow one. Its fields are
   the form's own pills (.field), its switch the Meals switch's colours. */
.pantry-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem 1rem;
    margin-bottom: 1.25rem;
    padding: 0.75rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    background: var(--surface);
    box-shadow: var(--shadow-card);
}
.pantry-bar__search { position: relative; flex: 1 1 14rem; min-width: 0; }
.pantry-bar__search input { padding-left: 2.6rem; }
.pantry-bar__icon {
    position: absolute;
    left: 1rem;
    top: 50%;
    transform: translateY(-50%);
    color: var(--muted);
    pointer-events: none;
}
.pantry-bar__sort { flex-direction: row; align-items: center; gap: 0.6rem; flex: 0 1 auto; min-width: 0; }
.pantry-bar__sort label { white-space: nowrap; }
.pantry-bar__sort select { width: auto; max-width: 100%; }
.pantry-bar__said { margin: -0.75rem 0 1rem; }

/* The quick filters: the segmented control of native radios (.segmented) -
   one tab stop, the arrows between them - in equal segments, with a thumb
   that slides under the one checked instead of its fill. Its count is a
   class, as the tabs' is, so that the thumb is one segment wide with no
   inline style. In forced colours the thumb goes and the checked one is
   .segmented's, bordered in Highlight. */
.pantry-filter {
    --count: 4;
    --at: 0;
    position: relative;
    display: grid;
    grid-template-columns: repeat(var(--count), minmax(0, 1fr));
    flex: 0 1 26rem;
    min-width: 0;
    margin: 0;
    border-radius: var(--pill);
}
.pantry-filter--count-2 { --count: 2; flex-basis: 14rem; }
.pantry-filter--count-3 { --count: 3; flex-basis: 20rem; }
.pantry-filter--count-5 { --count: 5; flex-basis: 27rem; }
.pantry-filter .pantry-filter__option {
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 40px;
    padding: 0 0.5rem;
    font-weight: 600;
    text-align: center;
}
/* The chosen segment keeps .segmented's fill, under its own words, so that
   they are read against it; it comes once the thumb has slid under it. */
.pantry-filter .pantry-filter__option:has(input:checked) { transition: background-color 0s linear 0.25s, color 0.15s; }
.pantry-filter__thumb {
    position: absolute;
    top: 4px;
    bottom: 4px;
    left: 4px;
    width: calc((100% - 8px - (var(--count) - 1) * 4px) / var(--count));
    border-radius: var(--pill);
    background: var(--accent-fill);
    transform: translateX(calc((100% + 4px) * var(--at)));
}
.pantry-filter:has(.pantry-filter__option:nth-of-type(2) input:checked) { --at: 1; }
.pantry-filter:has(.pantry-filter__option:nth-of-type(3) input:checked) { --at: 2; }
.pantry-filter:has(.pantry-filter__option:nth-of-type(4) input:checked) { --at: 3; }
.pantry-filter:has(.pantry-filter__option:nth-of-type(5) input:checked) { --at: 4; }
@media (prefers-reduced-motion: no-preference) {
    .pantry-filter__thumb { transition: transform 0.25s ease; }
}
@media (prefers-reduced-motion: reduce) {
    .pantry-filter .pantry-filter__option:has(input:checked) { transition: none; }
}
@media (forced-colors: active) {
    .pantry-filter__thumb { display: none; }
}
/* Where five segments would not fit at a readable size, .segmented as it
   is everywhere else: pills that wrap to a second row, the chosen one
   filled, and no thumb to slide. */
@media (max-width: 560px) {
    .pantry-filter { display: flex; flex-wrap: wrap; border-radius: 26px; }
    .pantry-filter .pantry-filter__option { flex: 1 0 30%; padding: 0 0.5rem; }
    .pantry-filter__thumb { display: none; }
    .pantry-filter .pantry-filter__option:has(input:checked) { transition: none; }
}
/* Show sends the bar without script; with it, the bar sends itself. */
.pantry-bar__apply[hidden] { display: none; }

/* Under the bar: the categories in the left quarter, the cards beside them;
   on a narrow screen the categories a row of chips above the cards. */
.pantry-page { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
.pantry-page__main { min-width: 0; }
.pantry-page__tools { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 1rem; margin: 0 0 1rem; }
.pantry-page__tools .meals-switch { margin-bottom: 0; }
.pantry-page__none { margin: 0 0 1rem; color: var(--text-2); }
.pantry-page__group + .pantry-page__group { margin-top: 1.5rem; }
.pantry-page__heading { font-size: 1.05rem; margin: 0 0 0.5rem; }
/* The Food filter's heading row (the owner's report on #531's page): the
   heading, + Add at the row's end on its centre line, and under the heading
   the key to the cards' yellow and red; where the heading is out of sight,
   the key in its place on the row. + Add's form opens under all of it,
   across the row - its <details> no box of its own where ::details-content
   is known (.inline-fold), else the whole fold under the row once open. */
.pantry-page__head {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "title add" "key key" "form form";
    align-items: center;
    column-gap: 1rem;
    margin: 0 0 1rem;
}
.pantry-page__head--keyed { grid-template-areas: "key add" "form form"; }
.pantry-page__head > .pantry-page__heading { grid-area: title; margin: 0; }
.pantry-page__head > .pantry-key { grid-area: key; }
.pantry-page__head > .pantry__add { grid-area: add; }
.pantry-page__head > .pantry__add[open] { grid-area: form; }
@supports selector(::details-content) {
    .pantry-page__head > .pantry__add > summary { grid-area: add; }
    .pantry-page__head > .pantry__add::details-content { grid-area: form; }
}
/* Its two entries on a line while they fit, each whole on a line of its
   own where they do not. */
.pantry-key {
    display: flex;
    flex-wrap: wrap;
    gap: 0.15rem 1.25rem;
    margin: 0;
    padding: 0;
    list-style: none;
    color: var(--text-2);
    font-size: 0.875rem;
}
.pantry-page__head:not(.pantry-page__head--keyed) > .pantry-key { margin-top: 0.15rem; }
.pantry-page__how { margin: 0 0 1rem; }

/* Each group a flex item of its own (#580): the Recipe filter's groups
   follow each other on a row while they fit, each one's chips wrapping. */
.pantry-categories { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.pantry-categories__group { min-width: 0; }
.pantry-categories__heading { margin: 0 0 0.25rem; padding: 0 0.9rem; color: var(--muted); font-size: 0.875rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; }
.pantry-categories__list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0.5rem; }
.pantry-categories__link {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    min-height: 44px;
    padding: 0 0.9rem;
    border: 1px solid var(--border-bright);
    border-radius: var(--pill);
    background: var(--surface);
    color: var(--text-2);
    font-size: 0.9rem;
    font-weight: 500;
    text-decoration: none;
}
.pantry-categories__link:hover { background: var(--surface-3); color: var(--text); }
.pantry-categories__link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.pantry-categories__link[aria-current="page"] { background: var(--accent-fill); border-color: var(--accent-fill); color: var(--on-accent); }
.pantry-categories__count { margin-left: auto; font-variant-numeric: tabular-nums; opacity: 0.8; }
.pantry-categories__empty { display: none; }

@media (max-width: 768px) {
    /* A heading out of sight as .visually-hidden puts it, still read as a
       heading; and a group with no chip shown left out, leaving no gap. */
    .pantry-categories__heading {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        padding: 0;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
    .pantry-categories__group:not(:has(li:not(.pantry-categories__empty))) { display: none; }
}

/* On the narrowest screens, where a group seldom fits a row, every chip
   flows on in one wrap (#580): a group's last chip alone on a row would
   be a break its hidden heading cannot explain. Inline boxes, so that the
   lists stay lists; no size to the spaces between them, so that only the
   margins part the chips, the last row's taken back below. */
@media (max-width: 25rem) {
    .pantry-categories { display: block; margin-bottom: -0.5rem; font-size: 0; }
    .pantry-categories__group,
    .pantry-categories__list { display: inline; }
    .pantry-categories__list > li:not(.pantry-categories__empty) { display: inline-block; margin: 0 0.5rem 0.5rem 0; vertical-align: top; }
}

@media (min-width: 769px) {
    .pantry-page { grid-template-columns: minmax(11rem, 1fr) minmax(0, 3fr); gap: 1.5rem; align-items: start; }
    /* Beside the cards wherever the page is scrolled to, below the sticky
       navbar, and never taller than the window under it: a side longer
       than that - the Recipe filter's in a laptop's window (#580) - scrolls
       on its own, as the week's days do (.week-days). Its padding is the
       focus ring's room, which a scroll box would cut off, kept as a link
       is scrolled to; its margin takes that back, so that it lines up. */
    .pantry-categories {
        position: sticky;
        top: calc(var(--nav-h) + 0.75rem);
        display: block;
        max-height: calc(100dvh - var(--nav-h) - 1.5rem);
        margin: -0.25rem;
        padding: 0.25rem;
        overflow-y: auto;
        overscroll-behavior: contain;
        scroll-padding-block: 0.25rem;
    }
    .pantry-categories__group + .pantry-categories__group { margin-top: 1rem; }
    .pantry-categories__list { flex-direction: column; gap: 0.25rem; }
    .pantry-categories__link { display: flex; border-color: transparent; background: transparent; border-radius: var(--radius-sm); }
    .pantry-categories__empty { display: list-item; }
    .pantry-categories__empty .pantry-categories__link { color: var(--muted); }
}

/* The cards: as many columns as fit, each at least 15rem, each a box of the
   pantry's own rows' look (.pantry__item, .sport__item, .recipes__item). */
.pantry-cards {
    list-style: none;
    margin: 0 0 1rem;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
    gap: 0.75rem;
    align-items: start;
}
.pantry-card {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.35rem;
    min-width: 0;
    margin: 0;
    padding: 0.75rem 0.9rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface);
    box-shadow: var(--shadow-card);
    overflow-wrap: anywhere;
}
.pantry-card > .pantry__row { flex: none; }
.pantry-card__facts { margin: 0; color: var(--text-2); font-size: 0.875rem; }
.sport__splits { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.15rem; font-size: 0.9rem; }
.sport__split-name { font-weight: 600; }
.pantry-card .sport__stock { margin: 0; }

/* ── A list's product, tapped to tick it (#579, ADR-0140) ──── */
/* A pantry card whose name is a link stretched over the whole card, to its
   dialog: the card is the target, and Change how you buy it is a control
   of its own above that link. Its one focus ring is the card's - the link's
   own is put out, over .card-section's for a link, which would draw a
   second inside it. Bought, a green edge - its border and a ring inside
   it, so nothing moves - and a check mark in its upper left corner, on the
   done role's text colour, which keeps the mark well clear of 3:1; the
   name, every line of it, makes room for it; and its name says so too.
   In an aisle's fold as in the Shopping filter, the cards' own grid - each
   card as tall as its row, so that a row reads as one, and Change how you
   buy it at its foot, its words in the middle of its 44px. */
.buy-card { position: relative; align-self: stretch; }
.buy-card:hover { border-color: var(--border-bright); }
/* Brought into sight by the keyboard, the link brings its card's top and
   its ring too: the card's padding and border above it, and 0.5rem. */
.buy-card__open { display: block; color: var(--text); text-decoration: none; scroll-margin-top: calc(0.75rem + 1px + 0.5rem); }
.buy-card__open:hover { color: var(--text); }
.buy-card__open::after { content: ""; position: absolute; inset: 0; border-radius: var(--radius-md); }
.buy-card .buy-card__open:focus-visible { outline: none; }
.buy-card:has(.buy-card__open:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.buy-card__about { display: grid; gap: 0.2rem; }
.buy-card__about > p { margin: 0; }
.buy-card__amount { font-variant-numeric: tabular-nums; }
.buy-card__how { position: relative; z-index: 1; display: flex; align-items: center; margin-top: auto; padding-block: 0; }
.buy-card__check { display: none; }
.buy-card--bought,
.buy-card--bought:hover {
    border-color: var(--success-fill);
    box-shadow: inset 0 0 0 1px var(--success-fill), var(--shadow-card);
}
.buy-card--bought .buy-card__check {
    display: block;
    position: absolute;
    top: 0;
    left: 0;
    width: 1.5rem;
    height: 1.5rem;
    padding: 0.3rem;
    border-radius: calc(var(--radius-md) - 1px) 0 var(--radius-sm) 0;
    background: var(--success-text);
    color: var(--surface);
}
.buy-card--bought .buy-card__open { padding-inline-start: 1rem; }

/* What a list's ticks did, and Undo: in sight over the list while the list
   is, under the bar - out of sight, still a live region, while it says
   nothing. */
.buy-said {
    position: sticky;
    top: calc(var(--nav-h) + 0.5rem);
    z-index: 5;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.25rem 0.75rem;
    margin: 0 0 0.75rem;
    padding: 0.4rem 0.5rem 0.4rem 1rem;
    min-height: 44px;
    box-sizing: border-box;
    border: 1px solid var(--border-bright);
    border-radius: var(--radius-md);
    background: var(--surface);
    box-shadow: var(--shadow-panel);
    color: var(--text);
    font-size: 0.92rem;
}
.buy-said:has(> .buy-said__words:empty) {
    position: absolute;
    width: 1px;
    height: 1px;
    min-height: 0;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}
/* Undo stays on the words' line on a 320px phone, so that the line is as
   short as it can be. */
.buy-said__words { flex: 1 1 8rem; }
.buy-said__undo { margin-left: auto; }

/* The page's one dialog for its cards: in the middle of the window, as a
   modal over a scrim - and as the page without script draws it, open, the
   page still behind it (`:not(:modal)`). Its scroll stays its own. */
.buy-dialog {
    --buy-pad: 1.25rem;
    width: min(28rem, calc(100% - 2rem));
    max-height: calc(100% - 2rem);
    margin: auto;
    padding: 1.125rem var(--buy-pad) var(--buy-pad);
    /* What the browser brings into view as it takes the focus - the field
       the dialog opens on, each control the keyboard reaches - clear of
       the foot that stays over the bottom of its scroll (WCAG 2.4.11):
       the foot's height, and room for a focus ring, at the top too. */
    scroll-padding-block: 0.5rem calc(0.5rem + 52px + var(--buy-pad) + 0.5rem);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface);
    color: var(--text);
    box-shadow: var(--shadow-panel);
    overflow: auto;
    overflow-wrap: anywhere;
}
.buy-dialog:modal { overscroll-behavior: contain; }
.buy-dialog::backdrop { background: var(--scrim); overflow: hidden; overscroll-behavior: contain; }
.buy-dialog[open]:not(:modal) { position: fixed; inset: 0; z-index: 150; box-shadow: var(--shadow-panel), 0 0 0 100vmax var(--scrim); }
/* Made to fit (ADR-0149): one column no wider than the dialog - a long
   figure scrolls in its field rather than widen a row past the window. */
.buy-dialog__form { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1rem; }

/* The head: the name, its aisle under it, and the one line of context. */
.buy-dialog__head { display: grid; gap: 0.125rem; }
.buy-dialog__title { margin: 0; font-size: 1.3rem; line-height: 1.25; }
.buy-dialog__aisle { margin: 0; font-size: 0.875rem; line-height: 1.4; color: var(--muted); }
.buy-dialog__aisle:empty { display: none; }
/* Each half whole where it fits, so that a narrow line breaks between them,
   a margin after the first where a space would collapse at a break. The dot
   is the second's, hung off its start into that margin: between the two on
   one line, and where the second starts a line, out past the line's start,
   clipped - never a dot left at the end of a line; and none without a first. */
.buy-dialog__context {
    --buy-dot: 0.9em;
    margin: 0.375rem 0 0;
    font-size: 0.9375rem;
    line-height: 1.4;
    color: var(--text-2);
    overflow: clip;
}
.buy-dialog__for,
.buy-dialog__held { display: inline-block; }
.buy-dialog__for { margin-inline-end: var(--buy-dot); }
.buy-dialog__for[hidden] { display: none; }
.buy-dialog__held { position: relative; }
.buy-dialog__dot { position: absolute; inset-inline-end: 100%; width: var(--buy-dot); text-align: center; }
/* Drawn, never read: the line is the dialog's description, which a
   browser reads out of it as text, out of sight from 768px or not. */
.buy-dialog__dot::before { content: "·"; content: "·" / ""; }
.buy-dialog__for[hidden] + .buy-dialog__held > .buy-dialog__dot { display: none; }
/* The line is the facts said short: while they are in sight, it is not. */
.buy-dialog:has(.buy-dialog__more[open]) .buy-dialog__context { display: none; }

/* More about it: its summary a 44px target reaching up into the gap above,
   so that it sits close under the line it tells more of - and, closed, a
   little into the gap below, so that the amount follows at the form's
   rhythm, not a 44px row's. */
.buy-dialog__more { margin-top: -0.875rem; }
.buy-dialog__more > summary {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    min-height: 44px;
    color: var(--link);
    font-size: 0.9375rem;
    font-weight: 600;
    line-height: 1.3;
    list-style: none;
    cursor: pointer;
}
.buy-dialog__more:not([open]) > summary { margin-bottom: -0.375rem; }
.buy-dialog__more > summary::-webkit-details-marker { display: none; }
.buy-dialog__chevron { flex: none; transition: rotate 0.15s; }
.buy-dialog__more[open] .buy-dialog__chevron { rotate: 180deg; }

/* The facts in full, on a quiet ground: each key in the first column, as
   wide as the widest, its words in the second. */
.buy-dialog__facts {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    column-gap: 1rem;
    margin: 0.125rem 0 0;
    padding: 0.125rem 0.875rem;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
}
.buy-dialog__fact {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: subgrid;
    align-items: baseline;
    padding: 0.5rem 0;
    border-top: 1px solid var(--border);
}
.buy-dialog__fact:first-child { border-top: 0; }
.buy-dialog__fact dt { font-size: 0.875rem; line-height: 1.4; color: var(--muted); }
.buy-dialog__fact dd { margin: 0; font-size: 0.9375rem; line-height: 1.4; color: var(--text); }
.buy-dialog__use { display: block; }
.buy-dialog__when { font-weight: 600; }
.buy-dialog__dishes { color: var(--text-2); }
.buy-dialog__note { color: var(--muted); }

/* The amount: "Bought" and what the list says on one row - what the list
   says under it, at its start, where both do not fit; the quick amounts;
   the field between - and +; why it was refused. */
.buy-dialog__field { gap: 0.625rem; }
.buy-dialog__bought {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.125rem 1rem;
}
.buy-dialog__bought > label { font-size: 1rem; }
.buy-dialog__listed { margin: 0; font-size: 0.9375rem; line-height: 1.4; color: var(--text-2); }
/* The figure is what tells grams from packs: the words' own colour. */
.buy-dialog__listed [data-buy-listed] { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }

/* The quick amounts: equal segments on a quiet track, the one the field
   holds filled as a chosen control is. */
.buy-dialog__quick {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 4px;
    padding: 4px;
    border: 1px solid var(--border);
    border-radius: var(--pill);
    background: var(--surface-2);
}
.buy-dialog__quick[hidden] { display: none; }
.buy-dialog__pick {
    min-width: 0;
    min-height: 44px;
    padding: 0.25rem 0.3rem;
    /* There, and clear, for forced colours to draw. */
    border: 1px solid transparent;
    border-radius: var(--pill);
    background: transparent;
    color: var(--text-2);
    font-family: var(--font-body);
    font-size: 0.92rem;
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
    transition: background-color 0.15s, color 0.15s;
}
.buy-dialog__pick[aria-pressed="true"] { background: var(--accent-fill); color: var(--on-accent); }
/* The app's ring, as on every control of the dialog, offset to fit the
   track's padding and the gap between the segments. */
.buy-dialog__pick:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The field between - and +, all three one height; under each sign, its
   step, clear of the round's edge: the sign's box drawn into the step's
   line, so that the words sit in the round's wide middle, and a step
   wider than that widens its button into a pill. - and + are ghost
   buttons (ADR-0094), as a date preset is, drawn as the field is - under
   a finger too: a touch screen keeps a hover after a tap, and a ghost's
   own hover, outside @media (hover: hover), is not theirs. */
.buy-dialog__stepper { display: flex; align-items: stretch; gap: 0.5rem; }
.buy-dialog .buy-dialog__step {
    flex: none;
    min-width: 3.5rem;
    min-height: 56px;
    flex-direction: column;
    gap: 0;
    padding: 0.25rem 0.75rem;
    border: 1px solid var(--input-border);
    background: var(--surface);
    color: var(--accent);
    line-height: 1.15;
}
.buy-dialog__step[hidden] { display: none; }
.buy-dialog__step > svg { margin-block: -3px; }
.buy-dialog__by { color: var(--text-2); font-size: 0.875rem; font-weight: 600; line-height: 1; white-space: nowrap; }

/* The pill, drawn as .field input is - its edge, hover and focus ring -
   the figure and its unit in its middle as one: "750 g". The field sizes
   to what is typed, no wider than the pill, and the pill is a label of
   it, so that a tap anywhere in it is in the field. */
.buy-dialog__amount {
    flex: 1 1 0;
    min-width: 0;
    min-height: 56px;
    display: flex;
    align-items: baseline;
    justify-content: center;
    padding-inline: 0.75rem;
    border: 1px solid var(--input-border);
    border-radius: var(--pill);
    background: var(--surface);
    cursor: text;
    transition: border-color 0.15s, background-color 0.15s;
}
.buy-dialog__amount:focus-within {
    border-color: var(--accent);
    background-color: var(--surface);
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}
.buy-dialog__amount:has(> input[aria-invalid="true"]) { border-color: var(--missed-text); }
.buy-dialog .buy-dialog__amount > input,
.buy-dialog .buy-dialog__amount > input:hover,
.buy-dialog .buy-dialog__amount > input:focus {
    field-sizing: content;
    flex: 0 1 auto;
    width: auto;
    min-width: 1ch;
    max-width: 100%;
    min-height: 54px;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: transparent;
    outline: none;
    color: var(--text);
    text-align: center;
    font-size: 1.35rem;
    font-weight: 600;
    line-height: 1.2;
    font-variant-numeric: tabular-nums;
}
/* Where a field cannot size to what is typed, it fills the pill, its
   figure at its end beside the unit. */
@supports not (field-sizing: content) {
    .buy-dialog .buy-dialog__amount > input,
    .buy-dialog .buy-dialog__amount > input:hover,
    .buy-dialog .buy-dialog__amount > input:focus { flex: 1 1 0; width: 0; text-align: end; }
}
.buy-dialog__amount > [data-buy-unit] {
    flex: none;
    margin-left: 0.3rem;
    font-size: 1.05rem;
    font-weight: 400;
    color: var(--text-2);
    pointer-events: none;
}
.buy-dialog__amount > [data-buy-unit][hidden] { display: none; }
.buy-dialog__field .form-error { margin: 0; min-height: 0; }

.buy-dialog__went p { margin: 0 0 0.35rem; }
.buy-dialog__went p:last-child { margin-bottom: 0; }

/* Accept and Cancel, of one height, Accept the wider and saying what it
   puts in; Take it out of your pantry on one line beside a Keep its own
   width - and on a phone too narrow for both on one line, across the
   foot, Keep as wide under it. Kept in sight at the dialog's foot
   while what is above them scrolls under them - a refusal's words on the
   shortest phone - their ground reaching out to the dialog's edges and
   down over its padding. */
.buy-dialog__buttons {
    position: sticky;
    /* The dialog's padding is inside what it sticks to: its ground reaches
       over it. */
    bottom: calc(-1 * var(--buy-pad));
    z-index: 1;
    flex-wrap: nowrap;
    align-items: stretch;
    justify-content: stretch;
    gap: 0.625rem;
    margin: -0.5rem calc(-1 * var(--buy-pad)) calc(-1 * var(--buy-pad));
    padding: 0.5rem var(--buy-pad) var(--buy-pad);
    background: var(--surface);
}
.buy-dialog__buttons > .btn { min-height: 52px; padding-inline: 1.1rem; font-size: 0.97rem; }
.buy-dialog__buttons > .btn-primary,
.buy-dialog__buttons > .btn-secondary { flex: 1 1 auto; }
.buy-dialog__buttons > .btn-ghost { flex: 0 0 auto; min-width: 6.5rem; }
/* Take it out takes the room left on its line, Keep only what it is left
   alone on one: the line's room is shared out a thousand to one. */
.buy-dialog__buttons:has(> .btn-secondary:not([hidden])) { flex-wrap: wrap; }
.buy-dialog__buttons:has(> .btn-secondary:not([hidden])) > .btn-secondary { flex-grow: 1000; }
.buy-dialog__buttons:has(> .btn-secondary:not([hidden])) > .btn-ghost { flex: 1 0 auto; min-width: 0; }
.buy-dialog__buttons:has(> .btn-secondary:not([hidden])) > .btn { padding-inline: 1rem; }
/* What Accept puts in, its figure and unit together: onto a line of its
   own where it does not fit beside "Accept", and parted at its space only
   where it does not fit on one either - never a word broken in two, which
   the dialog's overflow-wrap does only where no space will. Its own space
   before it is the first of its line, and gone: a margin in its place. */
.buy-dialog__accept-amount { display: inline-block; margin-inline-start: 0.3em; font-variant-numeric: tabular-nums; }
.buy-dialog__accept-amount:empty { margin-inline-start: 0; }

/* Hover where there is one: a touch screen keeps a hover after a tap. */
@media (hover: hover) {
    .buy-dialog__more > summary:hover { color: var(--link-hover); }
    .buy-dialog__more > summary:hover > span { text-decoration: underline; text-underline-offset: 3px; }
    .buy-dialog__pick:hover { background: var(--surface-3); color: var(--text); }
    .buy-dialog__pick[aria-pressed="true"]:hover { background: var(--accent-fill-hover); color: var(--on-accent); }
    .buy-dialog .buy-dialog__step:hover { background: var(--surface-2); border-color: var(--input-border); color: var(--accent); }
    .buy-dialog__amount:hover { background-color: var(--surface-2); }
}

/* A phone under 360px: three quick amounts - the list's among them, the
   one farthest from it left out - each - and + its sign alone, its step
   still in its name ("Less, 50 g"); the line's two halves a comma apart,
   rather than a dot; and each fact's key run into its words. */
@media (max-width: 359px) {
    .buy-dialog { --buy-pad: 1rem; padding-top: 1rem; }
    .buy-dialog__form { gap: 0.875rem; }
    .buy-dialog__more { margin-top: -0.75rem; }
    .buy-dialog__quick { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .buy-dialog__pick--spare { display: none; }
    .buy-dialog__by {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        overflow: hidden;
        clip-path: inset(50%);
    }
    .buy-dialog__context { --buy-dot: 0.3em; }
    .buy-dialog__dot { display: none; }
    /* Out of what is read, where a browser takes alternative text: the
       comma read out is the one out of sight already. */
    .buy-dialog__for:not(:has(+ .buy-dialog__held[hidden])) > [data-buy-gist="for"]::after { content: ","; content: "," / ""; }
    .buy-dialog__buttons > .btn-ghost { min-width: 5.5rem; padding-inline: 0.9rem; }
    .buy-dialog__facts { padding-inline: 0.75rem; }
    .buy-dialog__fact { display: block; padding: 0.45rem 0; }
    .buy-dialog__fact dt,
    .buy-dialog__fact dd { display: inline; }
    .buy-dialog__fact dt { margin-right: 0.4rem; }
    .buy-dialog__use:first-child { display: inline; }
}
@media (forced-colors: active) {
    .buy-card--bought { outline: 2px solid Highlight; outline-offset: -3px; }
    .buy-card--bought .buy-card__check { forced-color-adjust: none; background: Highlight; color: HighlightText; }
    /* The quick amount the field holds, as a chosen control is drawn. */
    .buy-dialog__pick { border-color: ButtonText; }
    .buy-dialog__pick[aria-pressed="true"] { forced-color-adjust: none; background: Highlight; color: HighlightText; border-color: Highlight; }
    .buy-dialog__pick:focus-visible { outline-color: Highlight; }
    .buy-dialog__amount:focus-within { outline-color: Highlight; }
}

@media (max-width: 768px) {
    .pantry-bar { border-radius: var(--radius-md); }
    .pantry-bar__search, .pantry-filter { flex: 1 1 100%; }

    .pantry-bar__sort { flex: 1 1 100%; }
    /* Its longest option is wider than a 320px phone: the field takes the
       row's room, not the option's, and the chosen one is cut to fit. */
    .pantry-bar__sort select { flex: 1 1 0; width: 0; min-width: 0; text-overflow: ellipsis; }
}
.pantry-page__group > div > .field-hint:first-child { margin-bottom: 0.75rem; }

/* ── Weigh-in around a session (#534, ADR-0135) ───────────── */
/* Once the session synced, the post-activity step is edged in the accent and
   flagged with a word and a mark on an inset - never the colour alone. The
   tokens are the theme's, so it holds in light and in dark. */
.around--synced { border: 2px solid var(--accent); }
.around__flag {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    margin: 0 0 0.75rem;
    padding: 0.5rem 0.75rem;
    border-left: 4px solid var(--accent);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    color: var(--text);
}
.around__icon { flex: none; margin-top: 0.15rem; color: var(--accent); }
.around__lead { margin: 0.5rem 0 0.25rem; }
.around__next { margin: 1.25rem 0 0; font-size: 1rem; }
/* The carbohydrate answer's legend reads as the labels beside it. Its chips
   are a row of their own under the boxes: the legend on one line, the four
   in a row, and no box beside them that they would not line up with. */
.around .group > legend { margin-bottom: 0.45rem; font-size: 0.92rem; font-weight: 600; color: var(--text); }
.around .form-grid > .group { grid-column: 1 / -1; }
.around .chip { font-weight: 500; }
