/*
 * Koræi — visual system.
 *
 * Ported from the reference mockup (index.html): the same shapes, spacing, glow
 * and typography, with the palette narrowed to black and neon pink. Cyan
 * survives only where a second colour genuinely carries meaning, so nothing
 * competes with the accent.
 *
 * Everything is self-hosted, fonts included, which is what lets the
 * Content-Security-Policy stay at default-src 'self' with no exceptions - and
 * keeps visitors' IP addresses away from a font CDN (spec §5.2, §7.5).
 */

/*
 * The `hidden` attribute has to actually hide.
 *
 * The browser hides it with `[hidden] { display: none }` from its own stylesheet, and
 * ANY author rule that sets `display` beats that - so `.btn { display: inline-flex }`
 * quietly made every hidden button visible again. That is not theoretical: the tracker
 * shipped with "Salva nota" still on screen after script had hidden it, and a "Salvata"
 * confirmation that was never hidden in the first place, so it sat there permanently
 * and told the athlete nothing. One rule, and every `hidden` in the application means
 * what it says.
 *
 * !important is the point rather than a shortcut: the attribute must outrank whatever
 * class the element happens to carry, which is exactly what the browser intended and
 * failed to achieve on its own.
 */
[hidden] { display: none !important; }

/* ---------- self-hosted fonts ---------- */
@font-face {
  font-family: 'Manrope';
  src: url('/fonts/manrope-latin-400-normal.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: 'Manrope';
  src: url('/fonts/manrope-latin-600-normal.woff2') format('woff2');
  font-weight: 600;
  font-display: swap;
}
@font-face {
  font-family: 'Manrope';
  src: url('/fonts/manrope-latin-700-normal.woff2') format('woff2');
  font-weight: 700;
  font-display: swap;
}
@font-face {
  font-family: 'Manrope';
  src: url('/fonts/manrope-latin-800-normal.woff2') format('woff2');
  font-weight: 800;
  font-display: swap;
}
@font-face {
  font-family: 'Barlow Condensed';
  src: url('/fonts/barlow-condensed-latin-500-normal.woff2') format('woff2');
  font-weight: 500;
  font-display: swap;
}
@font-face {
  font-family: 'Barlow Condensed';
  src: url('/fonts/barlow-condensed-latin-600-normal.woff2') format('woff2');
  font-weight: 600;
  font-display: swap;
}

/*
  Colour tokens.

  Every colour in this stylesheet comes from here, which is the whole reason a
  light theme is a dozen overrides rather than a rewrite. :root is the dark
  theme; [data-theme="light"] on <html> swaps the surfaces and the text.

  Two independent axes, and keeping them independent is what makes four accents
  cost eight small blocks instead of eight stylesheets:

    data-theme  = dark | light          -> surfaces, text, shadows
    data-accent = pink | green | orange | tiffany -> the accent tokens only

  The accent tokens used to be called --pink, which stopped being true the day
  the athlete could choose Tiffany. They are --accent* now: same values, honest
  name, and every rule in this file already went through them.

  The light theme exists for a real situation, not for taste: outdoors in
  sunlight a black screen is close to unreadable, and an athlete checking their
  session mid-ride is exactly the person who will be outdoors.
*/
:root {
  color-scheme: dark;

  /*
    NOTHING UNDER 12px. There were thirty-odd declarations between 9px and 11.5px in here - the
    eyebrows, the units, the tile captions, the meta under a meal - and on a phone in daylight they
    were the small print of an application whose whole job is to be read at a glance, one-handed,
    often outdoors. Davide asked for a floor and 12px is it. Where something needed to look smaller
    than what it labels, it is the weight and the colour that carry that now, not the size.
  */

  /*
    Tiffany, fixed. Every other colour here follows the accent the athlete chose; this one marks
    things that are not about them and not controls - where a workout came from, and the training
    icons. A constant colour is what makes those findable without reading them.
  */
  --tiffany: #2ee6df;
  --tiffany-line: rgba(46, 230, 223, .45);

  --bg: #08080b;
  /*
    Four steps of grey, and the two quiet ones were too quiet. Measured against #08080b: --mut was
    6.2 : 1 and --mut2 only 3.2 : 1, which is under the 4.5 : 1 that body text needs - and those two
    carry the hints, the units and the tile captions, the small print that explains what every number
    on the dashboard means. On a phone in daylight it read as dark grey on black.

    Lifted to 8.8 : 1 and 6.2 : 1, and deliberately still grey: --ink is the white one, and if every
    step were white there would be no hierarchy left to read. Each remains distinctly quieter than
    the one above it.
  */
  --ink: #f2f3f7;
  --ink-2: #dcdfe7;
  --mut: #a8acba;
  --mut2: #8b8f9c;

  --line: rgba(255, 255, 255, .09);
  --line-soft: rgba(255, 255, 255, .06);

  /*
    Card face, top and bottom of its gradient. OPAQUE, and that is the whole point of these two
    values being written as hex rather than as the rgba(255,255,255,.045) they replace.

    A translucent white only looks dark because --bg is painted behind it. Every time the browser
    composites a card against something else, the card turns white - and the ink on it is white
    too, so the card empties. That is not hypothetical: it is what a full-page screenshot on iOS
    does to the two tallest cards on the dashboard, which get their own compositing layer and are
    rasterised against the blank page rather than against the body. Everything inside them goes with
    it, because --accent-soft, --field and --track are translucent as well and were compositing
    against that same white. Printing does the same thing, for the same reason.

    These are the exact colours the old values produced over #08080b, so nothing on screen changes.
    An opaque card also means the fills inside it composite against the card itself, which is what
    makes one translucent value work on a card, a panel and a bar alike - so those stay as they are.
  */
  --surface: #131316;
  --surface-2: #0d0d10;
  /* Quieter fills: tiles, inputs, chips. */
  --field: rgba(255, 255, 255, .03);
  --field-soft: rgba(255, 255, 255, .015);
  --track: rgba(255, 255, 255, .07);

  /* Bars and floating panels are opaque: content scrolls behind them. */
  --bar: rgba(8, 8, 11, .97);
  --panel: rgba(16, 16, 22, .99);
  --scrim: rgba(0, 0, 0, .6);
  --shadow-card: 0 10px 34px rgba(0, 0, 0, .35);
  --shadow-panel: 0 18px 44px rgba(0, 0, 0, .6);

  /*
    THE ACCENT: ONE VALUE PER COLOUR, and it is --accent-base. Everything else on this page is
    derived from it, in both modes, which is why adding a fifth colour is a single line.

    Derived rather than hand-picked twice because two hand-written variants drift: the first time
    somebody tunes the dark pink and forgets the light one, the app has two different pinks and
    nobody notices for a month. color-mix() does the tuning, and the percentages below were chosen
    by measuring contrast, not by eye - see the light block.
  */
  --accent-base: #ff1e8e;
  --accent: var(--accent-base);

  /*
    THE SECOND BRAND COLOUR, and the reason it exists: the official logo is a gradient, not a colour.
    The ring runs from the accent at its foot to a cooler cousin at its head, and "æi" carries the
    same run. One value per palette, exactly like --accent-base, so a new accent is two lines rather
    than a hunt through the stylesheet.

    Every pairing is the accent's own neighbour on the wheel rather than a fixed blue: a cold blue
    beside orange is two brands arguing. Pink gets the light blue of the logo as drawn, which is
    where this started.
  */
  --accent-2: #4cc9f0;
  --accent-soft: color-mix(in srgb, var(--accent) 9%, transparent);
  --accent-line: color-mix(in srgb, var(--accent) 50%, transparent);
  /* Near-black ink on a filled accent button. Measured against all four bases: dark ink scores
     5.7 to 15.5, white ink 1.3 to 3.6 - so one value serves every colour on dark surfaces. */
  --on-accent: #0a0006;
  /* Accent-coloured text on the page background, lifted towards white so it stays legible at 13px. */
  --accent-ink: color-mix(in srgb, var(--accent) 55%, white);
  /* Accent-coloured text, as its own token so it can be darkened without touching the colour
     itself. Identical to the accent today, in both modes: the athlete asked for one colour that does
     not change, and this is the seam to pull if a neon on white turns out to be too pale to read. */
  --accent-text: var(--accent);

  /* Fluorescent means the colour bleeds past its own edge: a tight core and a wide wash.
     GRAPHICS ONLY. These went on headings, values and the brand word as a text-shadow, and a glow
     on a glyph eats the very edge that makes it a letter - at 13px a number stops being crisp and
     starts being a smudge in the accent colour. Rings, bars, the gauge and the microphone keep it;
     nothing with text in it does. */
  --glow-accent: 0 0 10px color-mix(in srgb, var(--accent) 55%, transparent),
                 0 0 26px color-mix(in srgb, var(--accent) 35%, transparent);
  --glow-accent-strong: 0 0 14px color-mix(in srgb, var(--accent) 80%, transparent),
                        0 0 44px color-mix(in srgb, var(--accent) 45%, transparent),
                        0 0 110px color-mix(in srgb, var(--accent) 20%, transparent);
  /* The quiet one. It used to light every button; it now lights one floating control, the chat's
     jump-to-latest, where the shadow is separating a round button from the conversation scrolling
     under it rather than advertising anything. A single tight layer, no wide wash - a page of these
     beside the strong glow's 110px halo is a smear in the accent colour, which is what it became. */
  --glow-accent-soft: 0 0 12px color-mix(in srgb, var(--accent) 22%, transparent);

  --danger: #ff5c5c;
  --danger-ink: #ffb3b3;
  --ok: #7ee787;
  /* Il gradino di mezzo fra --ok e --danger: "ne resta, ma non tanto". Dichiarato qui accanto agli
     altri due perche' un colore semantico che vive in un solo punto del foglio e' un colore che il
     prossimo reinventa leggermente diverso. */
  --warn: #f0b429;

  --r: 22px;
  --r-sm: 16px;
  --bar-h: 70px;
  /*
    I due caratteri, con un nome ciascuno.

    --sans veniva gia' usato da una regola ed era una variabile che non esisteva: una var() senza
    valore rende la dichiarazione invalida e la proprieta' torna a ereditare, quindi il carattere
    giusto arrivava per caso, perche' body e' Manrope. Definito qui, e la pila completa e' scritta
    una volta sola invece che in quattro punti del foglio.
  */
  --sans: 'Manrope', -apple-system, 'Segoe UI', Roboto, sans-serif;
  --cond: 'Barlow Condensed', 'Arial Narrow', sans-serif;
}

/*
  Light theme.

  Not an inversion. Neon pink on white at full strength is unpleasant and fails
  contrast on small text, so the accent is darkened for text while the fills stay
  the brand pink, and the glows are cut right back - a glow on a white background
  reads as a printing error.
*/
/*
  Il secondo selettore e' la vetrina, che un attributo data-theme non ce l'ha e non puo' averlo: li'
  non c'e' un profilo su cui scrivere la preferenza, quindi il comando e' una casella di spunta e
  :has() la legge da :root - lo stesso meccanismo con cui la stessa pagina sceglie l'accento.

  Scritto prima e non dopo di proposito: AccentTest cerca la stringa `:root[data-theme="light"] {`
  per leggere questo blocco e controllare che il chiaro non ridipinga l'accento. Messo in coda al
  gruppo, quel testo resta dov'era.
*/
:root:has(.lp-theme-light:checked),
:root[data-theme="light"] {
  color-scheme: light;

  --bg: #f6f6f8;
  --ink: #16161c;
  --ink-2: #35353f;
  /*
    THE TWO QUIET GREYS, MEASURED AGAINST THE DARKEST THING THEY LAND ON.

    --mut2 was #83879a: 3.30 : 1 on this background, under the 4.5 : 1 body text needs, and it carries
    the small print - the units, the hints, the tile captions. The same defect was found and fixed in
    the dark palette by reading the dashboard outdoors; here nobody had measured, and the light theme
    shipped with it.

    Then measured again on the painted page rather than against the page colour, which is what found
    the rest of it. Neither grey sits only on white: they land on the tile fill (#eeeef0 composited),
    the metric fill (#e2e2e4) and the setup banner (#d9d8da), and on that last one --mut itself was
    4.40 : 1 and --mut2 3.56.

    So both are set against the worst ground and not the best. --mut is 4.91 : 1 on the banner and
    6.46 on the page; --mut2 is 4.61 and 6.07. Still two distinct steps, still both quieter than
    --ink-2 above them: the hierarchy is what these four levels are for.
  */
  --mut: #555967;
  --mut2: #595d6c;

  --line: rgba(16, 16, 28, .14);
  --line-soft: rgba(16, 16, 28, .09);

  --surface: #ffffff;
  --surface-2: #ffffff;
  --field: rgba(16, 16, 28, .035);
  --field-soft: rgba(16, 16, 28, .02);
  --track: rgba(16, 16, 28, .10);

  --bar: rgba(255, 255, 255, .98);
  --panel: #ffffff;
  --scrim: rgba(16, 16, 28, .38);
  --shadow-card: 0 6px 20px rgba(16, 16, 28, .07);
  --shadow-panel: 0 18px 44px rgba(16, 16, 28, .18);

  /*
    THE ACCENT DOES NOT CHANGE BETWEEN MODES. One coded value per colour, and light mode uses the
    same one at the same strength, glow included - the surfaces change, the accent does not. That is
    the point of having two independent axes.

    It used to darken the accent by half here and drop the glows. The reasoning was contrast, and the
    numbers are worth keeping written down: neon green at full strength on #f6f6f8 measures 1.4 : 1,
    where body text wants 4.5 : 1. It is fine for a filled button (dark ink on a bright fill), fine
    for a border, fine for a ring, and hard to read as small text.

    So if accent-coloured *text* on the light background ever needs help, the knob is --accent-text
    below: it is the accent everywhere it is a colour, and the one place to darken if it has to be
    read at 13px. Nothing else needs touching, and no colour has to be picked twice.
  */
  --accent-soft: color-mix(in srgb, var(--accent) 12%, transparent);
  --accent-line: color-mix(in srgb, var(--accent) 55%, transparent);
  /* Dark ink on a bright fill: measured 5.7 to 15.5 against all four bases, where white scores
     1.3 to 3.6. The same value serves both modes for the same reason. */
  --on-accent: #0a0006;
  /*
    Mixed half and half, down from 70%. At 70% the green resolved to #1eb25f and measured 2.77 : 1 on
    white, and Tiffany 3.16 - so accent-coloured text was illegible on a light page for two of the
    four palettes, in the one token whose entire job is being legible. At 50% the worst of the four is
    the green at 4.99 : 1 and the best the pink at 10.11.
  */
  --accent-ink: color-mix(in srgb, var(--accent) 50%, black);
  /*
    And the knob, turned. This is the value links and other accent-coloured text take; on the dark
    theme it is the accent itself, here it is the darkened one. The fills, borders, rings and glows
    keep the brand colour at full strength - only text moves, which is the whole reason this is a
    separate token from --accent.
  */
  --accent-text: var(--accent-ink);

  --danger: #c62828;
  --danger-ink: #8c1d1d;
  --ok: #1a7f37;
  /* Piu' scuro del gemello notturno: su fondo chiaro un ambra brillante non si legge. */
  --warn: #a35c00;

}

/*
  ---------- accents ----------

  One line per colour. Nothing else: the tokens above derive the soft fills, the lines, the ink and
  the glows from whichever base is in force, and the light block darkens that same base by half. A
  fifth colour is a value here, a constant in the Accent enum and a label in the two bundles.

  Tiffany is the one named after a colour that already exists in the world: #0ABAB5, brightened here
  because the original goes muddy on black.
*/
:root[data-accent="green"] { --accent-base: #2bff88; --accent-2: #2ee6df; }
:root[data-accent="orange"] { --accent-base: #ff7a1a; --accent-2: #ffd23f; }
:root[data-accent="tiffany"] { --accent-base: #2ee6df; --accent-2: #6ea8ff; }
/*
  Le due palette che spostano anche le superfici.

  Le quattro qui sopra sono un accento su un fondo solo: nero quasi puro, uguale per tutte. Queste
  due sono nate come palette intere - "Tactical Performance" e "Clinical Bio-Tech" - e la meta' del
  loro carattere sta nel fondo: il volt su nero vero e' una torcia in una stanza buia, sull'asfalto
  e' una striscia su una strada; e la menta chiede l'ardesia, o resta il Tiffany su nero che c'e'
  gia'.

  I due valori dell'accento restano in una regola loro, come per tutte le altre - vedi AccentTest,
  che pretende esattamente due codici colore per accento e non tollera che ne compaia un terzo li'
  dentro. Le superfici sono una seconda regola, sotto: chi aggiunge un accento continua a scriverne
  due, e chi vuole anche un fondo scrive un blocco in piu' invece di allargare il primo.

  :not([data-theme="light"]) e non una seconda regola per il chiaro. Le superfici del tema chiaro
  sono dichiarate piu' in alto in questo file, quindi un blocco per accento scritto qui sotto le
  batterebbe per ordine di sorgente e l'atleta che passa al chiaro si ritroverebbe l'asfalto. Con la
  negazione questi due blocchi semplicemente non si applicano al chiaro, che si tiene i suoi bianchi
  - e' una decisione sulla luce in cui si sta, e un fondo antracite al sole non e' piu' leggibile del
  bianco. Ed e' anche cio' che tiene in piedi la regola di AccentTest: nessun accento scrive una
  seconda tavolozza per il chiaro.

  I due selettori raggruppati sono le due vie per arrivare allo stesso posto: l'attributo che scrive
  il server per chi ha un account, e il radio spuntato sulle pagine pubbliche, che un account non ce
  l'hanno. Raggruppati e non ripetuti: le superfici sono cinque valori, e due copie sono due copie
  che prima o poi divergono - la vetrina mostrerebbe una palette che l'applicazione non ha.
*/
:root[data-accent="volt"] { --accent-base: #d7ff2e; --accent-2: #f2f4f7; }
:root[data-accent="ice"] { --accent-base: #4fe3b0; --accent-2: #7dd3fc; }

/* Asfalto: antracite opaco, non nero. Le carte salgono di un gradino invece di scendere, perche' su
   un fondo gia' grigio una carta piu' scura sembra un buco. */
:root:not([data-theme="light"])[data-accent="volt"],
:root:not([data-theme="light"]):not(:has(.lp-theme-light:checked)):has(.accent-pick[value="volt"]:checked) {
  --bg: #1a1c1e;
  --surface: #232629;
  --surface-2: #1e2124;
  --bar: rgba(26, 28, 30, .97);
  --panel: rgba(35, 38, 41, .99);
}
/* Ardesia: lo stesso quasi-nero con dentro del blu, cosi' la menta ha da dove staccarsi. */
:root:not([data-theme="light"])[data-accent="ice"],
:root:not([data-theme="light"]):not(:has(.lp-theme-light:checked)):has(.accent-pick[value="ice"]:checked) {
  --bg: #0a1017;
  --surface: #121a24;
  --surface-2: #0d141d;
  --bar: rgba(10, 16, 23, .97);
  --panel: rgba(18, 26, 36, .99);
}

/*
  ---------- lo stesso colore, scelto prima di avere un account ----------

  Le due pagine pubbliche - la vetrina e la registrazione - non hanno un atleta da cui leggere una
  preferenza, quindi non possono avere data-accent addosso: quell'attributo lo scrive il server, e
  qui non c'e' ancora niente da scrivere. Ma il colore si deve poter provare lo stesso, ed e' anche
  il modo piu' onesto di mostrare che l'applicazione lo lascia scegliere: si preme un pallino e la
  pagina cambia sotto le dita.

  Un gruppo di radio con classe .accent-pick, e :has() che guarda quale e' spuntato. Le due
  proprieta' finiscono su :root - lo stesso elemento su cui stanno le derivazioni li' sopra - quindi
  cambia tutto quello che ne discende: i riempimenti, le linee, l'inchiostro, i bagliori e il
  gradiente del marchio. Non una riga in piu' per ogni colore.

  Niente per il rosa: e' quello di :root, quindi il suo radio non ha bisogno di nessuna regola.

  Il valore, non l'id: sulla vetrina i radio si chiamano in un modo e nella registrazione sono il
  campo del form, ma il valore e' l'id dell'accento in tutti e due i casi - lo stesso che il server
  poi convalida contro l'enum.

  Dove :has() non c'e', il selettore e' invalido e la regola sparisce: la pagina resta rosa e i
  pallini non fanno niente. E' la degradazione giusta per una decorazione, e non serve una riga di
  JavaScript - che su queste pagine non c'e' proprio.
*/
:root:has(.accent-pick[value="green"]:checked) { --accent-base: #2bff88; --accent-2: #2ee6df; }
:root:has(.accent-pick[value="orange"]:checked) { --accent-base: #ff7a1a; --accent-2: #ffd23f; }
:root:has(.accent-pick[value="tiffany"]:checked) { --accent-base: #2ee6df; --accent-2: #6ea8ff; }
:root:has(.accent-pick[value="volt"]:checked) { --accent-base: #d7ff2e; --accent-2: #f2f4f7; }
:root:has(.accent-pick[value="ice"]:checked) { --accent-base: #4fe3b0; --accent-2: #7dd3fc; }

* {
  box-sizing: border-box;
  margin: 0;
  -webkit-tap-highlight-color: transparent;
}

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.5;
  min-height: 100vh;
}

/*
  ---------- the room behind the page ----------

  The background was honest and anonymous: one flat colour. This puts the application somewhere - a
  dark hall with one lamp on it, or a track at the end of the day.

  Drawn, not photographed. The references share a quality of light rather than any photographic
  detail, and light is what a vector draws well: five kilobytes against three hundred, which on a
  phone over 4G is the difference between a page that is there and a page that arrives. It is also
  ours, with nothing in this repository whose licence we would have to be able to prove.

  Below the accent wash and above the flat colour, so the athlete's palette still tints the page and
  the artwork never competes with it. position: fixed rather than background-attachment: fixed, which
  janks on iOS during scroll. pointer-events: none, because a decoration that can be tapped is a
  decoration that swallows a tap meant for a card.

  If it is noticeable, it is too strong: no light in either file is drawn above 9% opacity, and the
  text on top keeps the contrast it was measured at.
*/
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: url("/img/arena-dark.svg") center / cover no-repeat;
  opacity: .85;
}
:root:has(.lp-theme-light:checked) body::after,
:root[data-theme="light"] body::after {
  background-image: url("/img/arena-light.svg");
  opacity: 1;
}
/*
  And the athlete's own answer to "how much of this do you want".

  Faint is a judgement made on one screen. On a bright phone outdoors the shapes can read as smudges,
  and somebody who finds any texture behind text tiring should not have to live with it - so the
  selector in the top bar offers three steps and this is where they land. "off" removes the element
  rather than making it transparent: nothing to composite, nothing to paint, no reason to keep it.
*/
:root[data-background="soft"] body::after { opacity: .4; }
:root[data-theme="light"][data-background="soft"] body::after { opacity: .5; }
:root[data-background="off"] body::after { display: none; }

/* The ambient glow behind everything, straight from the mockup. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background:
    /* Both accent washes derive from the accent. The top-right one was a literal pink, so on any
       other colour the page kept a pink corner - the same defect as the volume bars, in the one
       place that covers the whole screen. */
    radial-gradient(620px 420px at 88% -4%, color-mix(in srgb, var(--accent) 16%, transparent), transparent 65%),
    radial-gradient(520px 380px at 0% 12%, color-mix(in srgb, var(--accent) 8%, transparent), transparent 65%),
    radial-gradient(760px 540px at 50% 108%, rgba(124, 58, 237, .10), transparent 65%);
}
/* The neon wash belongs to the dark theme; on a light page it just looks like a
   stain. Kept at a whisper so the light theme is not completely flat. */
:root:has(.lp-theme-light:checked) body::before,
:root[data-theme="light"] body::before {
  background:
    radial-gradient(620px 420px at 88% -4%, color-mix(in srgb, var(--accent) 5%, transparent), transparent 65%),
    radial-gradient(760px 540px at 50% 108%, rgba(124, 58, 237, .04), transparent 65%);
}

.app-shell {
  position: relative;
  z-index: 1;
  max-width: 520px;
  margin: 0 auto;
  padding: 16px 16px 96px;
}

button, input, select, textarea {
  font-family: inherit;
  color: inherit;
  font-size: inherit;
  outline: none;
}
button { cursor: pointer; }
input::placeholder { color: var(--mut2); }

/*
  Links take --accent-text, not the accent itself, and that one word is a real fix.

  On the dark theme the two are the same value and always have been: the neon on near-black measures
  5.2 to 15.0 : 1 depending on the palette. On the light theme the raw accent as 13px text measured
  1.23 : 1 for the green, 1.44 for Tiffany, 2.42 for the orange and 3.34 for the pink - against the
  4.5 : 1 body text needs. Every link in the application, and the stylesheet had already written down
  both the diagnosis and the cure beside the light palette: "if accent-coloured *text* ever needs help,
  the knob is --accent-text". Nobody had turned it. With it turned, the same four are 4.63 to 9.37.

  Found by measuring the painted page rather than the tokens - the token test passes either way,
  because --accent-text was correct and simply unused here.
*/
a { color: var(--accent-text); text-decoration: none; }
a:hover { text-decoration: underline; }

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

/* ---------- navbar ---------- */
nav.top {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 14px;
  max-width: 520px;
  margin: 0 auto;
  /* Sotto ci vuole quanto sopra. Era zero, e il pulsante del tema - alto 44 pixel, che e' l'area
     minima da toccare - appoggiava sul bordo inferiore della barra. Con un controllo tondo si
     vede subito: sembra scivolato fuori invece che dentro. */
  padding: 12px 16px;
  font-family: var(--cond);
  font-size: 14.5px;
  font-weight: 600;
  letter-spacing: 1.8px;
  text-transform: uppercase;
}
nav.top .logo {
  font-family: var(--sans);
  font-weight: 800;
  font-size: 20px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--accent-text);
}
nav.top a { color: var(--mut); }
nav.top a:hover { color: var(--ink); text-decoration: none; }
nav.top button {
  background: none;
  border: 1px solid var(--line);
  color: var(--mut);
  border-radius: 999px;
  padding: 7px 14px;
  font-family: var(--cond);
  font-size: 12.5px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
}
nav.top button:hover { border-color: var(--accent-line); color: var(--accent-text); }

/* ---------- hero: the logo, full size ---------- */
/*
  The five pages with no top bar - sign in, register, and the three error pages - carry the logo here
  instead, and it is the same logo: the fragment the bar renders, scaled up.

  It used to be the word alone with a gradient across all five letters. That was a second logo: the
  ring was missing, and "Kor" took the accent instead of the page's ink. On the one page every visitor
  sees before anything else. Reported from production with a photograph of it.

  A flex row rather than a line of text, because the mark is a lockup: ring, gap, word. Everything
  scales with the viewport so it fills a phone without overflowing a narrow one, and the ring's stroke
  is thinned in viewBox units - at 50px across, the bar's 2.4 would draw a 5px band.
*/
.brand {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(9px, 2.8vw, 15px);
  margin: 18px 0 2px;
  line-height: 1.05;
}
/* Il marchio e' un collegamento alla vetrina: e' l'anello piu' la parola, quindi il collegamento
   deve contenerli entrambi e non solo il testo. Nessuna sottolineatura e nessun colore suo - il
   marchio ha gia' i suoi, e un logo che si tinge di blu al passaggio non e' piu' il logo. */
.brand > a {
  display: flex;
  align-items: center;
  gap: inherit;
  color: inherit;
  text-decoration: none;
}
.brand .logo-ring {
  width: clamp(38px, 11vw, 50px);
  height: clamp(38px, 11vw, 50px);
}
.brand .logo-ring circle { stroke-width: 1.7; }
.brand .logo-word {
  font-size: clamp(40px, 12vw, 54px);
  letter-spacing: -1px;
}
.hero { padding-top: 8px; }
/*
  The greeting opens the page, above the ring and against the left margin: it is
  a sentence addressed to a person, and a centred sentence under a big number
  reads as a caption for that number instead.
*/
/* Directly under the brand, so it reads as "Koraei — hello, Luca" and not as a
   caption for whatever comes next. */
/* An h1 since the wordmark left the hero, so it is sized here rather than inheriting the
   browser's 2em heading. Same 21px it has always been - it reads as a greeting, not a banner. */
.greet {
  text-align: center;
  margin: 14px 0 6px;
  font-size: 21px;
  font-weight: 700;
  line-height: 1.2;
  color: var(--ink);
}
.greet b {
  color: var(--accent-text);
  font-weight: 700;
}

/* ---------- cards ---------- */
.card {
  position: relative;
  margin-top: 18px;
  padding: 22px 20px;
  border-radius: var(--r);
  background: linear-gradient(180deg, var(--surface), var(--surface-2));
  border: 1px solid var(--line-soft);
  box-shadow: var(--shadow-card);
}
/*
  A page's own title, and it is an h1 because it is the page's heading.

  Every page used to open with the wordmark at 44-58px, six centimetres under the same wordmark in
  the top bar - the product's name said twice before the page said what it was once, on a screen
  844px tall. The dashboard and the chat had already dropped it for that reason; this is the rest of
  the application catching up. The card's own heading was promoted in its place, so the page still
  has exactly one h1 and a screen reader is told where it is rather than which product it is.

  Same size as the h2 it replaces: nothing moves, the tag and the meaning changed.
*/
.card h1.page-title,
.card h2 { font-size: 24px; font-weight: 800; letter-spacing: -.2px; }
.card h3 { font-size: 18px; font-weight: 700; }

/*
  ---------- figures line up ----------

  Every place the application prints a number. In a proportional face a 1 is narrower than an 8, so
  "12,4 km" and "8,1 km" start at different places and a column of distances down a list wobbles;
  worse, a counter that ticks 9 -> 10 shifts the text beside it sideways. tabular-nums makes every
  digit the same width, which is what the numerals in a table were designed for.

  One rule listing them rather than a declaration in each: this is a single typographic decision, and
  spread across fourteen rules it is fourteen places for the next figure to be forgotten.

  Only figures. Body text keeps proportional numerals, where they read better inside a sentence.
*/
.gauge-score,
.history-fact,
.macro-amount,
.metric-value,
.nutrition-history-kcal,
.nutrition-kcal-value,
.nutrition-kcal-target,
.plan-card-count,
.plan-entry-count,
.planned-figure,
.planned-percent,
.session-when-value,
.tile-value,
.volume-value,
.zone-pct { font-variant-numeric: tabular-nums; }

/* Uppercase condensed micro-labels: the mockup's signature detail. */
.microlbl, .metric-label, .tile-label, .bubble-who, label {
  display: block;
  font-family: var(--cond);
  font-weight: 600;
  font-size: 12.5px;
  letter-spacing: 2.4px;
  text-transform: uppercase;
  color: var(--mut);
}
/* A label belongs above the field it names, never beside it. `display: block` is what
   makes that true even inside a flex row, where an inline label would otherwise sit on
   the same line as its input and squeeze it. The one exception is a checkbox, whose
   label reads as a sentence next to the box - handled in .checkbox-row below. */
label { display: block; margin: 16px 0 8px; }

/* ---------- forms ---------- */
/* Ogni campo in cui si scrive qualcosa, per tipo. La lista e' esplicita e va tenuta completa: un
   tipo che non compare qui non eredita niente e il browser lo disegna a modo suo - che e' come il
   campo "ora" della pianificazione e' finito a essere un rettangolo grigio squadrato in mezzo a
   pillole arrotondate. FormFieldStyleTest confronta questa lista con i tipi usati nei template. */
input[type="text"], input[type="email"], input[type="password"],
input[type="date"], input[type="time"], input[type="number"], textarea, select {
  width: 100%;
  border: 1px solid var(--line);
  background: var(--field);
  color: var(--ink);
  border-radius: 999px;
  padding: 13px 18px;
  font-size: 15px;
  appearance: none;
}
textarea { border-radius: var(--r-sm); }
input:focus, textarea:focus, select:focus { border-color: var(--accent-line); }

input[type="file"] {
  width: 100%;
  border: 1px dashed var(--line);
  background: var(--surface-2);
  color: var(--ink);
  border-radius: var(--r-sm);
  padding: 14px 16px;
  font-size: 14px;
}

.checkbox-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  /* 44px, the tap target the rest of the application uses. The row is usually taller than that
     already - the health-data consent runs to two lines - but a one-line row must not be smaller. */
  min-height: 44px;
  margin: 14px 0;
  padding: 4px 0;
  font-size: 14px;
  color: var(--ink-2);
}
/*
  20px, not the browser's 13.

  Reported from the registration screen: the biometric-consent box "is too small, make it match the
  other one". Measured, both boxes were the same 13x13 - the browser's default, which the rest of the
  interface had grown past. What differed was the text beside them: one line against two, so the same
  small square looked smaller next to the taller block, and it was the more important of the two
  consents. Both are 20px now, which is also a target a thumb can hit.

  flex: 0 0 auto, or the box is squeezed by the label as it wraps - which is what made it look
  narrower than square on a phone in the first place.
*/
.checkbox-row input[type="checkbox"] {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  margin: 0;
  accent-color: var(--accent-text);
}
.checkbox-row label {
  margin: 0;
  font-family: var(--sans);
  font-size: 14px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink-2);
}


/* ---------- buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  min-height: 44px;
  border: none;
  border-radius: 999px;
  padding: 16px 20px;
  margin-top: 18px;
  font-family: var(--cond);
  font-weight: 600;
  font-size: 16.5px;
  letter-spacing: 2.2px;
  text-transform: uppercase;
  text-decoration: none;
  /*
    NO GLOW HERE. Every button used to be lit, on the reasoning that an outlined button should read
    as lit and one tight layer is not a light show. Measured on a real dashboard it is: eight or
    nine controls down the page, each with a halo, and the one button that actually starts something
    is indistinguishable from the seven that expand a fold.

    The glow is what says "this is the thing to press", so it belongs to .btn-primary and nowhere
    else. A box-shadow still lights the shape rather than the letters when it is used - glow belongs
    to graphics, never to type - and that rule has not changed.
  */
}
.btn:hover { text-decoration: none; }
.btn:active { transform: scale(.985); }

/* The filled button is the loudest thing on its card, so it keeps the wide wash. */
.btn-primary {
  background: var(--accent);
  color: var(--on-accent);
  box-shadow: var(--glow-accent-strong);
}
.btn-secondary {
  background: var(--field);
  color: var(--ink);
  border: 1px solid var(--line);
}
.btn-secondary:hover { border-color: var(--accent-line); color: var(--accent-text); }
/* Its own colour, not the accent's: a delete button that glows in the brand colour is inviting. */
.btn-danger {
  background: rgba(255, 92, 92, .1);
  border: 1px solid rgba(255, 92, 92, .5);
  color: var(--danger-ink);
  box-shadow: 0 0 12px rgba(255, 92, 92, .18);
}

/* Compact, inline variant: sits next to a set's inputs or under one exercise's
   note, not stretched full width like a page-level action. */
/* Small in width, never in height: 44px is what a thumb needs whatever the label says. */
.btn-small {
  width: auto;
  min-height: 44px;
  padding: 8px 16px;
  margin-top: 0;
  font-size: 13px;
  letter-spacing: 1.4px;
}

/* ---------- messages ---------- */
/*
  Margin on both sides, not just the bottom. An alert usually follows a paragraph
  of explanation, and with a bottom margin only it sat right up against that text
  - two blocks of prose with a border drawn between them, reading as one.
*/
.alert {
  border-radius: var(--r-sm);
  padding: 12px 16px;
  margin: 18px 0 16px;
  font-size: 14.5px;
  line-height: 1.55;
}
/* At the very top of a card the heading already provides the separation. */
.card > .alert:first-child, .app-shell > .alert:first-child { margin-top: 0; }

.alert-error {
  background: rgba(255, 92, 92, .12);
  border: 1px solid rgba(255, 92, 92, .4);
  color: var(--danger-ink);
}
.alert-success {
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  color: var(--accent-ink);
}
/* Always under the field, never beside it. The margin does that in a normal block form;
   flex-basis is what does it inside .plan-inline and .plan-add, where a message inserted
   as a sibling would otherwise become another column of the row and push the inputs
   sideways. width: 100% keeps long messages from stretching the row. */
.field-error {
  color: var(--danger);
  font-size: 13px;
  margin-top: 6px;
  flex: 0 0 100%;
  width: 100%;
}
.muted { color: var(--mut); font-size: 14px; }
/* Provider credit and the link back to it, side by side and quiet: required by the
   Strava API Agreement, but it is their credit, not our headline. */
.attribution {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 10px;
  margin-top: 14px;
  font-size: 13px;
}
.attribution .muted { font-size: 13px; }

/* ---------- Strava card ----------
   Grouped rather than a single column of paragraphs: facts, then actions, then
   the credit on its own rule at the foot. The old stack gave "Sync now" and
   "Disconnect" the same visual weight, one above the other, which is a poor way
   to present one everyday action and one that deletes an athlete's history. */
.strava-card .strava-body { margin-top: 14px; }
.strava-card .strava-line { margin-top: 6px; }
.strava-card .strava-line:first-child { margin-top: 0; }

.strava-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-top: 16px;
}
/* Pushed to the far end, away from the thumb that just pressed Sync. On a narrow
   screen the row wraps and it simply lands on the next line, still last. */
.strava-actions .strava-danger { margin-left: auto; }

/* The quiet variant: legible, reachable, and not competing for the press. It
   turns red only on hover, when the intention is already there. */
.btn-quiet {
  background: none;
  border: 1px solid var(--line);
  color: var(--mut);
}
.btn-quiet:hover { border-color: var(--danger); color: var(--danger); }

/*
  ---------- the guided setup ----------

  Up to eight questions after the first login, one to a screen. Everything here is in service of one
  idea: the athlete should be able to see, without reading, how much is left and that they may stop.

  The bars are the progress - one per question this athlete is asked, filled when the account holds an
  answer and solid when it is the screen in front of you. The name of each is in the markup as hidden
  text, because a row of coloured bars tells somebody using a screen reader nothing at all, and the
  name of the current one is printed on the line below.

  Two ways out, styled apart on purpose: "I'll add this later" sits inside the card, under the question
  it skips, and "leave the setup" sits below the card. Neither uses the destructive quiet button of the
  rest of the application - that one turns red on hover, and skipping a question is not a dangerous act.
*/
.onboarding { max-width: 560px; margin: 0 auto; }
.onboarding-mark { display: flex; justify-content: center; margin: 6px 0 18px; }
.onboarding-mark .logo-ring { width: 26px; height: 26px; }
.onboarding-mark .logo-word { font-size: 22px; }

.onboarding-progress {
  display: flex;
  gap: 6px;
  list-style: none;
  padding: 0;
  margin: 0 0 14px;
}
/* A bar and nothing else. The names sit on the line below now: there are up to eight questions, and
   eight words across a phone came out at five characters each. The name is still in the markup for a
   screen reader, hidden with .sr-only. */
.onboarding-progress li {
  flex: 1;
  height: 3px;
  border-radius: 2px;
  background: var(--line);
}
/* Answered: filled, but softly. Current: the full accent, so the screen you are on reads loudest even
   when it is not the one furthest along. */
.onboarding-progress li.is-done { background: color-mix(in srgb, var(--accent) 45%, transparent); }
.onboarding-progress li.is-current { background: var(--accent); }

/* "Step 3 of 7 — Measurements". The count is of the questions this athlete is actually asked. */
.onboarding-where {
  display: flex;
  gap: 8px;
  align-items: baseline;
  margin: 0 0 14px;
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--mut2);
}
.onboarding-where-what { color: var(--ink); font-weight: 700; }

.onboarding-card { padding-bottom: 22px; }
.onboarding-card .step-body > p:first-child { margin-bottom: 16px; }

/* What each question is for, on the welcome screen; what a skipped one costs, on the closing one. */
.onboarding-list { list-style: none; padding: 0; margin: 18px 0; display: grid; gap: 14px; }
.onboarding-list li { display: grid; gap: 2px; }
.onboarding-list-what {
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--accent-text);
}

/* The doors on the sync question. Cards rather than a list of links: they are the only thing
   on that screen, and each one is a decision with a consequence written under it. */
.onboarding-routes { list-style: none; padding: 0; margin: 18px 0 0; display: grid; gap: 10px; }
.onboarding-route {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  min-height: 44px;
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--field);
  text-decoration: none;
  color: var(--ink);
}
.onboarding-route:hover { border-color: var(--accent-line); text-decoration: none; }
.onboarding-route .ico { flex: 0 0 auto; margin-top: 2px; color: var(--accent-2); }
.onboarding-route-text { display: grid; gap: 2px; min-width: 0; }
.onboarding-route-name { font-weight: 700; }

.onboarding-skip { display: flex; justify-content: center; margin-top: 14px; }
/* Not the destructive hover: leaving a question for later is an ordinary choice. */
.onboarding-skip .btn-quiet:hover { border-color: var(--accent-line); color: var(--accent-text); }

/* "I'll add this later", inside the card and under the question it skips - so it reads as belonging to
   that question, unlike the one below the card, which leaves the setup altogether. */
.onboarding-skip-step { display: flex; justify-content: center; margin-top: 16px; }
.onboarding-skip-step .btn-quiet:hover { border-color: var(--accent-line); color: var(--accent-text); }

/* A line of consequence under an action: what the button is about to do to the rest of the walk. */
.onboarding-aside { margin-top: 14px; font-size: 13px; }

/* The free-text goal box, set apart from the checkbox list it is an alternative to. */
.onboarding-card .goal-text {
  margin: 18px 0 4px;
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--field);
}
.onboarding-card .goal-text textarea { margin-bottom: 6px; }

/*
  ---------- the four goal areas, closed ----------

  Eighteen checkboxes in four headed lists was a screen and a half of small print on a phone, and the
  athlete had to read all of it to find the two that were theirs. Closed, the question is four rows.

  Opening is CSS and nothing else: the toggle is a checkbox, and `:checked ~ .goal-group-body` shows what
  follows it. Which is why the input is a sibling of the body rather than inside the label - a selector
  cannot climb back out of one. It works with scripting off, and app.js only adds the tidying-up when an
  area is closed again.

  The toggle is hidden but not display:none - it has to stay focusable and reachable by keyboard, and its
  label is the 44px row, so the whole row is the target.
*/
.goal-group { border-bottom: 1px solid var(--line); }
.goal-group:last-of-type { border-bottom: 0; }
.goal-group-toggle { position: absolute; opacity: 0; width: 1px; height: 1px; }
.goal-group-head {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  margin: 0;
  padding: 4px 2px;
  cursor: pointer;
  font-family: var(--cond);
  font-size: 13px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--mut);
}
/*
  The chevron, and why it is not a tick box.

  It was a tick box, and it read as one: reported from the live setup, an athlete who cycles as well as
  lifts saw the gym area open with a filled box and the other three closed with empty ones, and
  understood that he was only allowed to choose from the gym. He was not - the closed rows have always
  opened - but a control that looks like a checkbox says "selected", not "there is more inside".

  So it is the same rotating chevron as the schedules and the ask boxes: one meaning, one drawing,
  everywhere in the application. Written in text so it needs no asset.
*/
.goal-group-mark {
  flex: 0 0 auto;
  font-size: 18px;
  line-height: 1;
  color: var(--accent-text);
  transition: transform .15s ease;
}
.goal-group-mark::before { content: "\203A"; }
.goal-group-toggle:checked + .goal-group-head { color: var(--ink); }
.goal-group-toggle:checked + .goal-group-head .goal-group-mark { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) {
  .goal-group-mark { transition: none; }
}
/* What is chosen inside, so a closed area never hides an answer. app.js keeps it current. */
.goal-group-count {
  margin-left: auto;
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.2px;
  color: var(--accent-text);
  white-space: nowrap;
}
.goal-group-name { flex: 0 1 auto; }
/* The focus ring belongs on the row, since the input itself is not what the eye follows. */
.goal-group-toggle:focus-visible + .goal-group-head {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 6px;
}
.goal-group-body { display: none; }
.goal-group-toggle:checked ~ .goal-group-body { display: block; padding-bottom: 8px; }
.goal-weight { margin-top: 10px; }
/* What the reading found, named one per line: a wrong tick has to be visible before it is saved. */
.goal-suggested ul { margin: 6px 0 0; padding-left: 18px; }

/* "or, if you already have one" - a rule with a word on it, between the written schedule and the two
   ways of supplying your own. */
.onboarding-or {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 18px 0;
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--mut2);
}
.onboarding-or::before, .onboarding-or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}
.onboarding-gym-ai .muted { margin-top: 8px; font-size: 13px; }

/* The written-schedule card on the builder. Same shape as the camera card below it: one explanation,
   one control, one button. */
/* The two warnings on the scanner, set apart from the explanation above them: they are the two
   mistakes athletes actually made, and a reader who skims the paragraph still has to meet them. */
/*
  The figures of one session, folded.

  Three workouts on the card meant twelve tiles between the athlete and the rest of the page. The
  heading identifies the workout - sport, when, and which device sent it - and everything else is one
  tap away. Same chevron as the schedules, the ask boxes and the goal areas: one drawing for "there is
  more inside", everywhere in the application.
*/
/* .card-fold folds a whole card - the gym schedules use it, and so does the goals page's "what do
   you train". It used to be grouped here with .session-detail, the fold over one session's figures;
   that one is gone, because the last-session card now opens the newest workout outright and puts
   the others behind a single "altri X". */
/*
  ---------- un solo "espandi", sempre nello stesso posto ----------

  Segnalato: "in alcune sezioni espandi e' a destra, alcune al centro, metti il bottone sempre a
  destra e meno invasivo, e lo stile deve essere uguale per tutti i bottoni di quel tipo".

  Erano due controlli diversi nati in momenti diversi. Quello delle schede era una riga larga tutta
  la carta con un disco accentato da 26 pixel; quello sotto il commento del coach era un testo in
  accento con una freccina, in linea - e siccome l'eroe della home e' una colonna centrata, li' si
  centrava da solo. Da cui "alcune al centro": non era una scelta, era l'allineamento del genitore
  che passava attraverso.

  Adesso i due selettori condividono una regola sola. A destra per costruzione - margin-left: auto
  sul contenuto, che non dipende da come e' allineato il contenitore - e piu' leggero: via il disco,
  resta la freccina, che e' la meta' discreta delle due. In accento e non in grigio, perche' su
  questa interfaccia l'accento vuol dire "si puo' premere", e la piega che nessuno vedeva era
  esattamente il difetto che il disco era stato messo a curare.

  L'area da toccare resta di 44 pixel: e' data dall'altezza minima, non dalla dimensione del segno.
*/
/* Il segno della piega, uno solo per tutte: una freccina disegnata con due bordi, che gira quando
   la piega si apre. Era un disco bordato da 26 pixel con il fondo in accento, messo quando le
   pieghe non si leggevano come premibili; adesso a dire "si puo' premere" ci pensa l'etichetta in
   accento accanto, e il disco restava solo a pesare. */
/* Grande quanto la sua etichetta: a sei pixel con un bordo da uno e mezzo era una virgola accanto
   a un testo da dodici e mezzo, e non si leggeva come parte dello stesso controllo. */
.session-detail-mark {
  display: inline-block;
  flex: 0 0 auto;
  width: 9px;
  height: 9px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translateY(-3px);
  transition: transform .15s ease;
}
.card-fold[open] > summary .session-detail-mark { transform: rotate(-135deg) translateY(3px); }
@media (prefers-reduced-motion: reduce) {
  .session-detail-mark { transition: none; }
}
/* Quante schede ci sono dentro, cosi' la carta chiusa risponde comunque a "qui c'e' qualcosa?".
   Prima dell'etichetta, non spinto a destra: adesso e' la riga intera ad andare a destra, e un
   margin-left: auto qui dentro la riaprirebbe da un lato all'altro della carta. */
.card-fold-count { order: -1; color: var(--mut); }

/*
  ---------- the last seven days ----------

  Columns, and deliberately the same family as the weekly chart directly below it: the week by day,
  then the month by week, one above the other. Davide asked for exactly that, and it is also the
  cheaper reading - the eye learns one shape and applies it twice.

  Sixty pixels a column is what a phone gives, which is enough for the bar, the weekday, the date and
  one figure a line. Not enough for the sport's name, so the figure takes that discipline's colour -
  the same colour it has in the chart underneath, where the legend names it. A title attribute carries
  the name for a pointer, and the legend carries it for everybody else.

  Heights come from line-N and not bar-N: the bar-N classes set a width as well, so a 50% day would
  come out half as wide as its neighbours. That is what the line-N classes exist for.
*/
.day-bars {
  display: flex;
  align-items: flex-start;
  gap: 4px;
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
}
.day-col {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 0;
}
/*
  A fixed height, not flex: 1.

  Measured, and it was wrong the first time: with the bar box stretching to fill what the labels left
  over, a day with two workouts under it got a shorter box than a day with one - so the busier day drew
  the shorter bar. Two figures where its neighbour had one, and the chart said the opposite of the
  truth. Every bar box is the same height now and the figures hang below it, however many there are.
*/
.day-bar {
  width: 100%;
  height: 96px;
  display: flex;
  align-items: flex-end;
  border-radius: 8px 8px 0 0;
  background: var(--field);
}
.day-fill {
  width: 100%;
  min-height: 3px;
  border-radius: 8px 8px 0 0;
  background: linear-gradient(180deg, var(--accent),
                              color-mix(in srgb, var(--accent) 35%, transparent));
}
/* A day with nothing on it keeps its column and loses its ground: the gap is the rest day. */
.day-rest .day-bar { background: transparent; border-bottom: 1px dashed var(--line); }

.day-label { display: grid; justify-items: center; margin-top: 8px; line-height: 1.1; }
.day-weekday {
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--mut2);
}
.day-date { font-size: 12px; color: var(--mut2); font-variant-numeric: tabular-nums; }
/* Today is the column the athlete is standing in, and it is not finished yet. */
.day-today .day-weekday { color: var(--accent-text); }

.day-figures { display: grid; justify-items: center; gap: 2px; margin-top: 6px; }
.day-figure {
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--ink-2);
}
/* The discipline's own colour, the same one the weekly chart gives it. vol-accent for the athlete
   with a single discipline, whose chart is drawn in their chosen palette colour. */
.day-figure.vol-accent { color: var(--accent-text); }
.day-figure[class*="vol-c"],
.day-figure.vol-walking,
.day-figure.vol-hiking { color: var(--bar); }
.day-rest-word {
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--mut2);
}

.scan-warning {
  margin: 8px 0 0;
  padding-left: 10px;
  border-left: 2px solid var(--accent-line);
  font-size: 13px;
}

/*
  ---------- the three questions ----------

  They sit inside the two forms that ask for a plan, above the controls that were already there, so
  the athlete reads what is being asked before the button that spends the interaction.

  Boxed and set apart from the rest of the form: these are questions rather than settings, and one of
  them is about injuries. Two rows each because an answer is a sentence - "spalla destra operata nel
  2023, niente spinte sopra la testa" does not fit on one line at this width, and a field that hides
  the second half of the answer while it is typed is a field people give up on.
*/
.intake {
  display: grid;
  gap: 6px;
  margin-bottom: 14px;
  padding: 12px;
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  background: var(--field);
}
.intake-lead { margin: 0 0 4px; font-size: 12.5px; line-height: 1.45; }
.intake label {
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--mut2);
}
.intake textarea {
  width: 100%;
  resize: vertical;
  min-height: 56px;
  font: inherit;
  line-height: 1.4;
}
.intake .checkbox-row label { text-transform: none; letter-spacing: 0; font-size: 13px; }

.magic-write-form { display: grid; gap: 8px; margin-top: 12px; }
.magic-write-form select { max-width: 120px; }
.magic-write-form .muted { font-size: 13px; }

.onboarding-plan-form { margin-top: 18px; }
.onboarding-plan-form .muted { margin-top: 8px; font-size: 13px; text-align: center; }

/* A button that owns its line, for a screen with exactly one thing to do next. */
.btn-block { display: block; width: 100%; text-align: center; }

.strava-hint {
  margin-top: 8px;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--mut2);
}

.strava-status.is-ok { color: var(--ok); }
.strava-status.is-ok .dot { background: var(--ok); box-shadow: 0 0 8px var(--ok); }
.strava-status.is-error { color: var(--danger); }
.strava-status.is-error .dot { background: var(--danger); box-shadow: 0 0 8px var(--danger); }

/* The spinner replaces the status dot while a sync runs. A static "SYNC IN
   PROGRESS" on a page that never changes is indistinguishable from a page that
   has frozen; this is the part that says the waiting is expected. One element,
   one keyframe: no image, no script, nothing for the CSP to block. */
.spinner {
  width: 12px;
  height: 12px;
  flex: 0 0 auto;
  border-radius: 50%;
  border: 2px solid var(--accent-line);
  border-top-color: var(--accent-text);
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* Motion is the whole point here, so it is not simply switched off: the ring
   stays and pulses instead of turning, which still reads as "working". */
@media (prefers-reduced-motion: reduce) {
  .spinner { animation: spinner-pulse 1.6s ease-in-out infinite; }
}
@keyframes spinner-pulse { 50% { opacity: .35; } }

/* The credit, bottom left, on its own rule. Required by Strava's Brand
   Guidelines, and it belongs here rather than among the controls, where it read
   as one more thing to act on. */
.strava-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--line-soft);
}
.strava-credit {
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--mut2);
}
.strava-account { font-size: 13px; }

/* ---------- gym schedules ---------- */
.plan-cards { list-style: none; margin: 12px 0 0; padding: 0; }
.plan-cards li { margin-top: 10px; }
/* A card row holds the link (edit) and the "Esegui" form side by side - the two
   cannot nest, a <button>'s <form> inside an <a> is invalid markup. */
.plan-card-row { display: flex; align-items: stretch; gap: 8px; }
.plan-card-row form { flex: 0 0 auto; display: flex; }
.plan-card-link {
  display: block;
  flex: 1 1 auto;
  min-width: 0;
  padding: 12px 14px;
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  color: var(--ink);
  text-decoration: none;
}
.plan-card-link:hover { border-color: var(--accent-line); text-decoration: none; }
/* Name over count, not side by side. On the same line the uppercase "4 ESERCIZI - 13 SERIE"
   took whatever width it needed and squeezed the name against it, with a long name running
   into it outright. Stacked, both have the full width of the card. */
.plan-card-head {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.plan-card-name {
  font-weight: 600;
  /* A schedule can be called anything; one line, cut with an ellipsis, never wrapped
     into a card twice the height of its neighbours. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.plan-card-count {
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--mut2);
  line-height: 1.4;
}
.btn-run { align-self: center; }
/*
  On a phone the row stacks.

  Side by side, the "run this schedule" button took whatever width it needed and left the link
  about 150px, so the exercise names - the only reason to look at this card in the morning - were
  cut to "Split squat bul...". Measured at 390px. Below 520 the link gets the full width and the
  button sits under it, full width too, which is also the easiest thing to hit.
*/
@media (max-width: 520px) {
  .plan-card-row { flex-direction: column; }
  .plan-card-row form { width: 100%; }
  .btn-run { width: 100%; align-self: stretch; }
}

/* ---------- workout history ----------
   Reuses .plan-cards: a logged workout reads like a schedule card with facts under it. */
.history-facts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
}
.history-fact {
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--mut);
}
/* An unfinished workout is a normal thing to see in a list, not an error - so it is
   marked, in the accent colour, rather than hidden or flagged red. */
.history-open {
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--accent-text);
}
.history-pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 16px;
  font-size: 13px;
}
.plan-more { margin-top: 14px; font-size: 13px; }

/* One row per set: tick, number, load, rest, remove. */
.plan-sets { list-style: none; margin: 12px 0 0; padding: 0; }
.plan-set {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid var(--line-soft);
}
.plan-set:last-child { border-bottom: none; }
/* Done sets stay legible rather than disappearing: the athlete still reads them
   to know what they lifted. */
.plan-set.is-done .plan-set-load,
.plan-set.is-done .plan-set-index { color: var(--mut2); }

/* A submit button, not a checkbox: a checkbox that submits itself needs an inline
   handler, and script-src 'self' blocks those - a control that silently does
   nothing is worse than one that looks like a button. Sized for a thumb. */
.plan-tick {
  width: 34px;
  height: 34px;
  flex: 0 0 auto;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: none;
  color: var(--accent-text);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
}
.plan-tick:hover { border-color: var(--accent-line); }
.plan-set.is-done .plan-tick {
  border-color: var(--accent-line);
  background: var(--accent-soft);
}

.plan-set-index {
  flex: 0 0 auto;
  min-width: 18px;
  font-family: var(--cond);
  font-size: 12px;
  color: var(--mut2);
}
.plan-set-read { display: flex; flex: 1 1 auto; align-items: center; gap: 10px; min-width: 0; }
.plan-set-load { flex: 1 1 auto; min-width: 0; font-variant-numeric: tabular-nums; }
.plan-set-rest { flex: 0 0 auto; font-size: 13px; color: var(--mut2); }
.plan-set-remove { flex: 0 0 auto; margin: 0; }
.plan-exercise-notes { margin-top: 6px; }

/* ---------- "Modifica scheda" ----------

   A checkbox drives the whole card between reading and editing. Every edit form is in the page
   either way; this decides which of the two versions of a row is on screen, so the switch needs no
   script and no request. The forms are siblings-of-siblings of the toggle, which is why the toggle
   sits outside the first card. */
.plan-edit-toggle {
  position: absolute;
  left: -9999px;
  /* Not display:none and not hidden: a hidden input leaves the tab order, and the button that
     drives it has to stay reachable by keyboard. */
}
.plan-edit-btn { margin-left: 8px; cursor: pointer; }
/*
  Run it, delete it, edit it.

  Two on the first line and one on the second. All three were full-width buttons in a column, which
  reads as a menu rather than as one action with two others beside it - Davide asked for the delete
  next to "esegui" and, on second thought, at the same height rather than smaller. So the run button
  takes the room that is left, the delete takes what its label needs, and "modifica" wraps to a line
  of its own.
*/
.plan-actions {
  margin-top: 16px;
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: 8px;
}
/* The forms stretch with the row and the buttons fill them, so "esegui" and "elimina" come out the
   same height even though only one of them has a border. Matching the padding by hand got within
   two pixels, which is exactly the kind of nearly-right that reads as a mistake. */
.plan-actions form { margin: 0; display: flex; }
.plan-actions form .btn { flex: 1 1 auto; margin: 0; }
.plan-actions form:first-of-type { flex: 1 1 auto; min-width: 0; }
.plan-actions .plan-delete-form { flex: 0 0 auto; }
.plan-actions .plan-delete-form .btn { width: auto; padding: 16px 18px; }
.plan-actions .plan-edit-btn { flex: 1 0 100%; margin: 0; }
.plan-actions .btn { margin: 0; }

.plan-edit-form,
.plan-set-edit,
.plan-edit-btn .when-editing { display: none; }

.plan-edit-toggle:checked ~ .card .plan-edit-form,
.plan-edit-toggle:checked ~ .card .plan-set-edit {
  display: flex;
  flex-wrap: wrap;
  flex: 1 1 100%;
  gap: 8px;
  margin: 0;
}
.plan-edit-toggle:checked ~ .card .plan-set-read,
.plan-edit-toggle:checked ~ .card .plan-edit-btn .when-reading { display: none; }
.plan-edit-toggle:checked ~ .card .plan-edit-btn .when-editing { display: inline; }
/* While editing, the card says so. */
.plan-edit-toggle:checked ~ .card.plan-builder-exercise { border-color: var(--accent-line); }

.plan-set-edit input { margin: 0; flex: 1 1 90px; min-width: 0; }
.plan-set-edit input[type="number"] { flex: 0 0 86px; }
.plan-set-edit button { flex: 0 0 auto; }
/* The keyboard focus ring has to be visible on a control parked off-screen's behalf. */
.plan-edit-toggle:focus-visible ~ .card .plan-edit-btn { outline: 2px solid var(--accent); }

/* The add row wraps on a phone instead of squeezing four controls onto one line. */
.plan-add {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
}
.plan-add input { margin: 0; flex: 1 1 120px; min-width: 0; }
.plan-add input[type="number"] { flex: 0 0 96px; }
.plan-add button { flex: 0 0 auto; }

.plan-inline {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-top: 14px;
}
.plan-inline input { margin: 0; flex: 1 1 180px; min-width: 0; }
/* Full width, so it takes the whole first line and the input follows underneath
   rather than sharing the row with it. */
.plan-inline label { margin: 0 0 2px; flex: 0 0 100%; }

/* ---------- Schedule scan ----------
   Reads photos of an existing schedule - see ScheduleVisionService. The drop area
   is a <label> wrapping a hidden file input, so tapping anywhere on it opens the
   file picker without any script. */
.magic-fill-form { margin-top: 4px; }
.magic-fill-drop {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 22px 16px;
  border: 1px dashed var(--line);
  border-radius: var(--r-sm);
  color: var(--mut);
  font-size: 14px;
  text-align: center;
  cursor: pointer;
}
.magic-fill-drop:hover { border-color: var(--accent-line); color: var(--accent-text); }
.magic-fill-drop input[type="file"] {
  /* Not display:none: a hidden input is not focusable, which would drop keyboard
     access to the picker entirely. Visually hidden, still reachable by tab. */
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
}
.magic-fill-status {
  margin: 10px 0 0;
  font-size: 13px;
  color: var(--accent-text);
}
/* The names of the files actually chosen: "3 files" in the native control is no help
   when a model call is about to be spent on the wrong three. */
.magic-fill-chosen {
  margin: 10px 0 0;
  font-size: 13px;
  color: var(--ink);
  word-break: break-word;
}
.magic-fill-hint { margin: 6px 0 12px; font-size: 12.5px; }

/* ---------- Tracker (workout sessions) ----------
   Reuses the builder's .plan-set row: same tick, same index, same rest. What is
   new is a second column for what was actually done, and the deviation state. */
.session-set { align-items: flex-start; }
.session-set-plan {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  min-width: 90px;
}
.session-set-edit {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  flex: 1 1 220px;
  min-width: 0;
}
.session-set-edit input { margin: 0; flex: 1 1 70px; min-width: 0; }
/* Wide enough for "17,5" and a stepper. It was 72px, which clipped a NUMERIC(6,2) value to "30,0(" -
   a figure cut mid-digit, reported from a phone. The template now sends "30" rather than "30,00",
   and the extra room is what makes a real 17,5 fit beside it. */
.session-set-edit input[type="number"] { flex: 0 0 84px; }

/* One Salva for the exercise, under its sets, right-aligned so it reads as the end of the block
   rather than as another row of it. */
.session-exercise-save {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 12px;
  margin-top: 10px;
}
.session-exercise-save .btn { margin: 0; }

/*
  ---------- one more set than the schedule asked for ----------

  The same row as editing a set, one step quieter: a dashed top rule instead of a solid one, because
  it is an invitation rather than a record. It shares the input sizing above by reusing the
  declarations rather than restating them - two rules with the same three numbers is two rules that
  drift the first time either is touched.
*/
.session-set-add {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 10px;
  padding-top: 12px;
  border-top: 1px dashed var(--line-soft);
}
.session-set-add input { margin: 0; flex: 1 1 70px; min-width: 0; }
.session-set-add input[type="number"] { flex: 0 0 72px; }
.session-set-add .set-label { flex: 0 0 100%; }

/* Adding a whole exercise: a folded card at the foot of the workout, styled by .card-fold already.
   Only the hint under it needs anything of its own. */
.session-add-hint { margin: 10px 0 0; font-size: 12.5px; }

/*
  ---------- what was actually done ----------

  The recap of a finished workout: one row per exercise trained, its sets as chips.

  Chips and not a table. Four sets of "8 × 60 kg" as four rows is three lines of chrome for one line
  of content, and the question this answers - "what did I lift" - is read across, not down.
*/
.session-recap .card-top { align-items: baseline; }
.recap-list { list-style: none; margin: 4px 0 0; padding: 0; }
.recap-exercise { padding: 12px 0; border-top: 1px solid var(--line-soft); }
.recap-exercise:first-child { border-top: none; padding-top: 4px; }
.recap-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
}
.recap-name { font-weight: 600; font-size: 15.5px; }
/* The top set, which is how a session is quoted. Accent, because it is the figure the athlete came
   back to read. */
.recap-top {
  flex: 0 0 auto;
  font-family: var(--cond);
  font-size: 12.5px;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--accent-text);
}
.recap-sets {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 8px 0 0;
  padding: 0;
}
.recap-set {
  border: 1px solid var(--line-soft);
  background: var(--field-soft);
  border-radius: 999px;
  padding: 4px 11px;
  font-size: 13.5px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.recap-note { margin: 8px 0 0; font-size: 13px; }

/*
  ---------- keep these in the schedule? ----------

  Asked once when a finished workout holds exercises the schedule does not. A checkbox row each,
  ticked by default, and two buttons - keep the ticked ones, or none of them.

  The whole row is the label, so the tap target is the row rather than the 20px box.
*/
.adopt-list { list-style: none; margin: 12px 0 16px; padding: 0; }
.adopt-row { border-top: 1px solid var(--line-soft); }
.adopt-row:first-child { border-top: none; }
.adopt-row label {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  margin: 0;
  cursor: pointer;
}
.adopt-row input[type="checkbox"] { flex: 0 0 auto; width: 20px; height: 20px; margin: 0; }
.adopt-name { flex: 1 1 auto; min-width: 0; font-size: 15px; }
.adopt-sets {
  flex: 0 0 auto;
  font-family: var(--cond);
  font-size: 12.5px;
  letter-spacing: 1.2px;
  text-transform: uppercase;
}
.session-set-actual { flex: 1 1 auto; font-variant-numeric: tabular-nums; }
/* A changed set stays visible rather than flagged red: it is expected, not an
   error - the whole point of the tracker is that reality may differ from plan. */
.session-set.is-deviated .session-set-plan { text-decoration: line-through; color: var(--mut2); }
.plan-tick-static {
  border: 1px solid var(--line-soft);
  color: var(--mut2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.badge-deviated {
  flex: 0 0 auto;
  border-radius: 999px;
  padding: 4px 11px;
  border: 1px solid var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent-text);
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
}

/* ---------- when a session happened ----------
   Folded away under the workout's heading: the default is right for anyone tracking at the gym,
   and only somebody writing up a past session needs to open it. Quiet when closed - a line with
   the date on it - so the card still reads as a workout rather than as a form. */
.session-when { margin-top: 10px; }
.session-when > summary { cursor: pointer; list-style: none; }
.session-when > summary::-webkit-details-marker { display: none; }
.session-when-open {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--mut2);
}
.session-when-value { color: var(--ink-2); letter-spacing: .6px; }
.session-when form { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 10px 0 0; }
.session-when input[type="datetime-local"] { flex: 1 1 auto; min-width: 0; margin: 0; }
.session-when .btn { margin-top: 0; }
.session-when-hint { margin: 8px 0 0; font-size: 12.5px; }

.session-note { margin-top: 14px; }
.session-note label, .session-note-readonly { display: block; margin-bottom: 6px; font-size: 13px; color: var(--mut); }
.session-note textarea { width: 100%; resize: vertical; }

/* The coach's comment: same tone as a chat bubble from the coach, because it is
   the same voice giving the same kind of answer. */
.ai-feedback { margin-top: 16px; }
.ai-feedback-body {
  margin-top: 8px;
  padding: 13px 16px;
  border-radius: 14px;
  background: var(--accent-soft);
  border: 1px solid color-mix(in srgb, var(--accent) 26%, transparent);
  font-size: 14px;
  line-height: 1.55;
}
/*
  Il riquadro del commento, quando porta dentro anche l'ora.

  Il bordo e il fondo passano da .ai-feedback-body a questo contenitore, e il corpo li perde solo
  qui dentro: le altre tre schede che usano .ai-feedback-body - la lettura di una foto, il riassunto
  di una seduta, il commento su un esercizio - non hanno un'ora da mettere e restano come sono.
*/
.ai-feedback-box {
  margin-top: 8px;
  padding: 13px 16px;
  border-radius: 14px;
  background: var(--accent-soft);
  border: 1px solid color-mix(in srgb, var(--accent) 26%, transparent);
}
.ai-feedback-box > .ai-feedback-body {
  margin-top: 0;
  padding: 0;
  border: none;
  background: none;
  border-radius: 0;
}
/*
  La riga in fondo al riquadro: il comando che apre a sinistra, l'ora a destra.

  L'ora se ne va a destra con un margine automatico e non con un "space-between", perche' su un
  commento corto la piega non viene stampata affatto e con un figlio solo "space-between" non ha
  niente da distanziare: l'ora finirebbe a sinistra su alcune schede e a destra su altre.

  L'allineamento in fondo e non al centro: il comando e' alto 44px, che e' quanto serve a un dito,
  e centrare l'ora contro quei 44px la staccherebbe dal bordo di sotto invece che appoggiarla.
*/
.ai-feedback-foot {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  margin-top: 4px;
}
.ai-feedback-foot > .box-advice-when { margin: 0 0 4px auto; }
/* Dentro al riquadro il comando non ha piu' niente sopra da cui staccarsi. */
.ai-feedback-foot > .advice-more { margin: 0; }

.ai-summary { margin-top: 20px; }

/* ---------- disciplines on the profile ----------
   One row per sport: the tick on the left, the weekly target on the right. The
   target keeps its place whether or not the sport is ticked, so ticking one does
   not make the whole list jump. */
.discipline-list { list-style: none; margin: 4px 0 18px; padding: 0; }
/* Two lines, always: the name and its checkbox, then the weekly target underneath.
   As one flex row it wrapped only when the label was long, so "Corsa" kept its box on the
   first line at x=128 while "Escursionismo" pushed its box onto a second line at x=37 -
   seven rows, four of them out of line with the other three, and which ones depended on
   how long the word happened to be in the current language. Fixed rows cannot do that. */
/*
  La riga di una disciplina: il nome su una riga sua, e sotto i suoi bersagli affiancati.

  Era tutto incolonnato - la spunta, poi le sedute, poi i chilometri, uno sotto l'altro e rientrati
  di trenta pixel rispetto alla casella. Tre righe per dire due numeri, e i due numeri lontani fra
  loro quando sono la stessa domanda fatta in due unita'. Riportati sulla stessa riga e allineati a
  sinistra sotto la casella, come chiesto.

  Un flex che va a capo, non una griglia: il nome e la piega prendono tutta la larghezza, i bersagli
  no e stanno accanto finche' ci stanno - su uno schermo molto stretto scendono da soli invece di
  schiacciarsi.
*/
.discipline {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0 22px;
  padding: 14px 0;
  /* Piu' marcata della riga fra due obiettivi dentro una piega: sono due livelli diversi, e quando
     una piega e' aperta finiscono uno sotto l'altro. */
  border-bottom: 1px solid var(--line);
}
.discipline:last-child { border-bottom: none; }
.discipline-pick {
  display: flex;
  align-items: center;
  gap: 10px;
  /* Riga sua: il nome sta sopra i suoi numeri, non accanto. */
  flex: 0 0 100%;
  min-width: 0;
  margin: 0;
  cursor: pointer;
}
/* accent-color come .goal-pick, .proposal-pick e .checkbox-row: senza, queste erano le uniche
   caselle dell'applicazione a restare del blu di sistema. Vale anche per la pagina obiettivi, che
   monta lo stesso elenco. */
.discipline-pick input { width: auto; margin: 0; accent-color: var(--accent-text); }

/*
  ---------- "i tuoi obiettivi", dentro la disciplina ----------

  La piega che si apre sotto una disciplina, con dentro le stesse caselle che prima erano una carta
  a se' piu' in basso. La riga che la apre e' quieta - e' dentro una riga gia' spuntabile, e un
  secondo controllo forte li' dentro competerebbe con la spunta che conta.

  Larga quanto la riga: la riga della disciplina e' una griglia di colonne strette (la spunta, le
  sedute, i chilometri) e senza questo la piega finirebbe in una di quelle.
*/
/* Tutta la larghezza: la piega apre sotto i bersagli, non accanto a uno di essi. */
.goal-fold { flex: 0 0 100%; margin-top: 10px; }
/*
  Aperta, deve staccarsi da quello che viene dopo.

  Segnalato con una freccia fra "corsa libera" - l'ultimo obiettivo della corsa - e "palestra", che
  e' la disciplina successiva: le due liste si toccavano e non si capiva dove finisse una e
  cominciasse l'altra. Due segni invece di uno solo, perche' qui si sovrappongono due livelli e lo
  spazio da solo si legge come "un po' piu' di spazio", non come "questo sta dentro quello".
*/
.goal-fold[open] { padding-bottom: 8px; }
.goal-fold > summary {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  min-height: 44px;
  cursor: pointer;
  list-style: none;
  font-family: var(--cond);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--accent-text);
}
.goal-fold > summary::-webkit-details-marker { display: none; }
.goal-fold[open] > summary .session-detail-mark { transform: rotate(-135deg) translateY(3px); }
/* Dentro la piega gli obiettivi tornano una lista normale, con la riga di separazione che avevano
   nella carta: sono le stesse righe, spostate e non riscritte. Il rientro e il binario a sinistra
   non stanno piu' qui: li mette la regola in fondo al foglio che li mette a tutte le sezioni che si
   aprono, questa compresa. */
.goal-fold .goal-list { margin: 0; }

/* Le tre righe che sono una gara a se'. Nessuna casella di sedute accanto: la riga e' la spunta e
   la sua domanda, e la domanda sta sotto e rientrata come stanno i bersagli settimanali delle
   altre - stessa colonna, stesso stacco, cosi' le sette righe si leggono come una lista sola.

   display: block e non il suo inline: su uno span in linea il margine sinistro sposta solo l'inizio
   della prima riga, non i campi dentro, e i campi restavano incollati al bordo. Misurato. */
.discipline-race .goal-detail { flex: 0 0 100%; margin: 10px 0 0; }
.discipline-race .goal-detail label { margin-top: 12px; }
.discipline-race .goal-detail label:first-child { margin-top: 0; }
/* Indented to sit under the label rather than under the checkbox, so the column of boxes
   reads as belonging to the names above them. */
/*
  Un bersaglio: l'etichetta sopra e il campo sotto, come ogni altro campo dell'applicazione.

  column-reverse e non un riordino del markup: nel markup viene prima il campo e poi la sua unita',
  che e' l'ordine giusto per chi legge con lo screen reader - il campo porta gia' il proprio nome
  in aria-label, e l'unita' lo segue. A schermo l'ordine utile e' l'opposto, e questa e' la riga che
  lo dice senza toccare nessuno dei due.

  Nessun rientro: allineati a sinistra sotto la casella di spunta, non spostati di trenta pixel.
*/
.discipline-target {
  display: flex;
  flex-direction: column-reverse;
  align-items: flex-start;
  gap: 5px;
  margin: 10px 0 0;
}
/* Narrow on purpose: a full-width box for "3" invites someone to type a sentence. */
.discipline-target input { width: 84px; margin: 0; text-align: right; }

/*
  ---------- due discipline nello stesso giorno ----------

  Stessa forma delle due caselle accanto - etichetta minuscola sopra, comando sotto - perche' e' la
  terza cosa che si dice della stessa disciplina, non un'impostazione di un altro genere. Va a capo
  da sola su una riga sua: e' l'unica delle tre che porta un nome invece di un numero, e stretta
  quanto le altre non ci starebbe "camminata (passeggiata, tragitti)".
*/
.discipline-pair {
  display: flex;
  flex-direction: column-reverse;
  align-items: flex-start;
  gap: 5px;
  margin: 10px 0 0;
  flex: 1 1 100%;
}
.discipline-pair select { margin: 0; max-width: 260px; }
.discipline-pair-label {
  margin: 0;
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--mut);
}
.discipline-unit {
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--mut2);
  white-space: nowrap;
}

/* ---------- setup banner ----------
   Impossible to miss, impossible to mistake for an error. It sits between the bar
   and the page, spans the full width, and the whole strip is the link so there is
   no small target to hit on a phone. */
/* Stacked when there is more than one, tightly, so two outstanding tasks read as
   a short list rather than as two separate alarms. */
.setup-banners {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 14px;
}
.setup-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  max-width: 720px;
  width: 100%;
  margin: 0 auto;
  padding: 12px 16px;
  border: 1px solid var(--accent-line);
  border-radius: 14px;
  background: var(--accent-soft);
  color: var(--ink);
  text-decoration: none;
}
.setup-banner:hover { border-color: var(--accent-text); text-decoration: none; }
.setup-banner-text { font-size: 14px; line-height: 1.4; }
.setup-banner-cta {
  flex: none;
  font-size: 13px;
  font-weight: 600;
  color: var(--accent-text);
  white-space: nowrap;
}
.center-text { text-align: center; }
.mt-24 { margin-top: 24px; }
.lang-switch { display: flex; justify-content: center; align-items: center; gap: 10px; margin-top: 18px; }
.lang-switch a { display: inline-flex; align-items: center; min-height: 44px; padding: 0 6px; }

/*
  Disclosure rows reach 44px.

  Measured on the dashboard: most <summary> elements were 316px wide and 18 to 27px tall - a wide
  thin strip, which is the shape a thumb misses. Nothing here changes what they look like; the row
  is given the height it should always have had, and the content stays centred in it.

  Scoped to the ones on a card. The appearance pill in the top bar and the accordion headings that
  already size themselves are left alone.
*/
/*
  Un selettore per riga e il blocco subito sotto, senza commenti in mezzo al gruppo.

  Questa regola ha perso il suo blocco due volte, tutte e due per mano mia, tutte e due togliendo
  UN selettore dal gruppo con una sostituzione di testo. Un gruppo non e' un elenco di righe
  indipendenti: cancellarne una che sta in fondo si porta via le dichiarazioni di tutte, e quello
  che resta - quattro selettori senza corpo - si attacca alla regola successiva. Qui la successiva
  era .tiles, quindi le quattro pieghe si sono ritrovate addosso "display: grid" con le colonne
  delle piastrelle della home: da cui "RIPOSATO" e il "?" spalmati su due colonne invece che
  centrati sotto l'anello.

  Non fallisce niente e non si vede in un diff, perche' CSS valido resta - e' solo un altro CSS.
*/
.card .explain > summary,
.hero .explain > summary,
.nutrition-accordion > summary,
.macro-edit > summary {
  display: flex;
  align-items: center;
  gap: 8px;
  /* Misurata sulla home: la maggior parte dei riassunti era larga 316 e alta fra 18 e 27 - una
     striscia sottile, che e' la forma che un pollice manca. */
  min-height: 44px;
}

/* ---------- dashboard tiles ---------- */
.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: 12px;
  margin-top: 16px;
}
/*
  Tre righe condivise fra le caselle, non tre righe per casella.

  "Le cifre devono essere allineate verticalmente, non sfalsarle." Non lo erano perche' una
  etichetta sta su una riga - QUESTA SETTIMANA - e l'altra su due - BRUCIATE IN ALLENAMENTO - e la
  cifra partiva dove finiva la sua etichetta, dodici pixel piu' in basso in una delle due.

  Con subgrid le righe le decide la griglia madre e valgono per tutte e due: etichetta, cifra,
  piede. La riga dell'etichetta e' alta quanto la piu' alta delle due, quindi le cifre partono dallo
  stesso punto qualunque cosa ci sia scritto sopra. Per questo ogni casella ha esattamente tre
  figli, e per questo il piede e' un contenitore: quello che ci va dentro cambia da una casella
  all'altra, il numero di righe no.

  Dove subgrid non c'e', le caselle restano quelle di prima - impilate e leggibili, semplicemente
  non allineate: il fallback e' la pagina di ieri, non una pagina rotta.
*/
.tile {
  background: var(--field);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-sm);
  padding: 14px 16px;
}
@supports (grid-template-rows: subgrid) {
  .tiles { align-items: stretch; }
  .tile {
    display: grid;
    grid-row: span 3;
    grid-template-rows: subgrid;
    align-content: start;
  }
}
/* Il piede: tutto quello che sta sotto la cifra, in una riga sola della griglia. */
.tile-foot {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}
.tile-warn { border-color: var(--accent-line); box-shadow: 0 0 18px color-mix(in srgb, var(--accent) 14%, transparent); }
/*
  A tile reads as three lines: what it measures, the figure with its unit, then a
  plain-language hint. The figure and the unit sit on one baseline - the unit at
  normal weight and roughly half the size, so "173 load pts" reads as one value
  and not as two competing numbers.
*/
.tile-value {
  display: flex;
  align-items: baseline;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 6px;
}
.tile-value b {
  font-size: 27px;
  font-weight: 800;
  letter-spacing: -.3px;
  line-height: 1.1;
  color: var(--accent-text);
}
.tile-value em {
  font-family: var(--cond);
  font-size: 12.5px;
  font-style: normal;
  font-weight: 600;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--mut);
}
.tile-hint {
  display: block;
  margin-top: 4px;
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--mut2);
}

/* ---------- weekly volume bars ---------- */
/*
  L'altezza appartiene alle barre, non alla colonna intera.

  Erano 160 pixel per tutto - barre piu' etichette - con le barre a "flex: 1", cioe' a prendersi
  quello che avanzava. Misurato in pagina: le etichette ne occupavano 106 e alle barre ne restavano
  55, e nella vista per disciplina, dove sotto ogni settimana ci sono tre percentuali e tre valori,
  il grafico si riduceva a una striscia di qualche pixel sopra mezza schermata di numeri.
  "Perche' il volume settimanale e' cosi' schiacciato?" - per questo.

  Adesso la zona delle barre ha la sua altezza fissa e le etichette prendono lo spazio che serve
  loro, sotto. La carta cresce di quel tanto, che e' il prezzo giusto: le etichette sono testo da
  leggere, le barre sono un disegno da confrontare, e chi decide quanto e' alto il disegno non
  possono essere le righe di testo che gli stanno sotto.
*/
.volume-bars {
  list-style: none;
  display: flex;
  align-items: flex-end;
  gap: 8px;
  padding: 0;
  margin: 18px 0 0;
}
.volume-week {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
}
/*
  Novantasei pixel, gli stessi di .day-bar.

  "Queste colonne sono troppo allungate in verticale rispetto a quella dei tuoi ultimi sette giorni,
  adeguale". Erano 140 - alzate da 55 perche' le etichette sotto se le mangiavano - e a quel punto
  erano il disegno piu' alto della pagina, davanti al grafico che l'atleta ha indicato come misura
  giusta. Una sola altezza per tutte le barre dell'applicazione: quella.
*/
.volume-bar {
  width: 100%;
  height: 96px;
  flex: 0 0 auto;
  display: flex;
  align-items: flex-end;
  position: relative;
}
/*
  ---------- the target, drawn ----------

  A dashed line at the weekly goal, across every week. The bars say how the weeks compare with each
  other; this says whether they were enough, which is the question the athlete has.

  A box the height of the target with a line on its top edge, rather than a line positioned from the
  bottom: it reuses nothing and needs no extra arithmetic, and "height" is the only geometry the bars
  themselves use. Its own height classes - line-0 to line-100 - because the bar-N classes set width
  as well and this must stay full width.

  Behind the bars (z-index 0 against their auto), so a bar that clears the target covers the line
  rather than being cut in half by it. That is also what makes the line read as a threshold: what is
  above it is drawn over it.
*/
.volume-target {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 0;
  pointer-events: none;
  border-top: 1px dashed color-mix(in srgb, var(--ink) 38%, transparent);
}
.volume-bar .volume-fill { position: relative; z-index: 1; }
.line-0 { height: 0; }
.line-5 { height: 5%; }
.line-10 { height: 10%; }
.line-15 { height: 15%; }
.line-20 { height: 20%; }
.line-25 { height: 25%; }
.line-30 { height: 30%; }
.line-35 { height: 35%; }
.line-40 { height: 40%; }
.line-45 { height: 45%; }
.line-50 { height: 50%; }
.line-55 { height: 55%; }
.line-60 { height: 60%; }
.line-65 { height: 65%; }
.line-70 { height: 70%; }
.line-75 { height: 75%; }
.line-80 { height: 80%; }
.line-85 { height: 85%; }
.line-90 { height: 90%; }
.line-95 { height: 95%; }
.line-100 { height: 100%; }
.volume-fill {
  width: 100%;
  min-height: 3px;
  border-radius: 8px 8px 0 0;
  /* Both stops derive from the accent. The foot of the gradient used to be a literal pink, so a
     bar switched to green, orange or Tiffany kept a pink base and the chart was the one place on
     the page that did not follow the colour the athlete had chosen. */
  background: linear-gradient(180deg, var(--accent),
                              color-mix(in srgb, var(--accent) 35%, transparent));
  box-shadow: 0 0 14px color-mix(in srgb, var(--accent) 25%, transparent);
}

/*
  ---------- a bar per discipline ----------

  Requested as running beside lifting rather than one total. The height of each is that
  discipline's percentage of its OWN weekly target - the only thing kilometres and sessions have in
  common - and the figure under it is the absolute one in the unit that discipline is counted in.

  Two or three thin bars in the space one used to have, with a hair of gap between them so they
  read as separate quantities rather than a stacked total. They are NOT stacked: stacking would
  imply the percentages add up to something, and 117% of a running target plus 50% of a gym target
  is not a number.
*/
.volume-bar-split { gap: 3px; }
.volume-bar-split .volume-fill { border-radius: 6px 6px 0 0; }

/*
  The colours.

  One discipline uses the accent the athlete chose, which is what .vol-accent falls through to -
  the base .volume-fill gradient already derives from --accent, so it needs no rule of its own and
  is listed here only so the intent is written down somewhere.

  Several use fixed fluo variants, one per discipline, assigned by the discipline's position in the
  athlete's own list so a colour never moves between two weeks or two visits. Every one is a
  saturated neon in the same register as the accent: this palette had to stay recognisably the same
  family, not become a pastel chart bolted onto a neon page.

  Defined as a --bar colour and consumed once, exactly like the nutrition macros above: the
  template carries a class per discipline and names no colour at all.
*/
.vol-c1 { --bar: #ff2d95; }  /* fluo pink   */
.vol-c2 { --bar: #2ee6ff; }  /* fluo cyan   */
.vol-c3 { --bar: #b06cff; }  /* fluo violet */
.vol-c4 { --bar: #38ff9e; }  /* fluo green  */
.vol-c5 { --bar: #ffb020; }  /* fluo orange */
.vol-c6 { --bar: #ffe94d; }  /* fluo yellow */
.vol-c7 { --bar: #f2f6ff; }  /* near white  */

/*
  ---------- the sports that are not training ----------

  A walk and a hike get a colour of their own, outside the positional palette. Reported from a
  screenshot of the seven-day chart: a 1,7 km walk was drawn in the accent, which is the colour
  running has, under a legend naming only Corsa and Pesi - so a walk to the shops read as a short
  run. "La camminata non deve essere letta come corsa."

  Yellow because Davide asked for yellow, and fixed by sport rather than by position because these
  turn up irregularly: a colour handed out by position would move between two visits.

  Two values per sport, one per theme, and that is the whole reason these are tokens. The bright
  yellow that reads on a black card is close to invisible on a white one - "renderò il colore giallo
  nell'elenco delle attività svolte in modalità scura e scuriscono in modalità chiara". The light
  values are darkened until they clear 4.5 : 1 on the light background, because this colour is text
  in the seven-day chart, not only a bar: #8a6a00 measures 4.65 : 1 and #5f6b00 measures 5.35 : 1.
*/
.vol-walking { --bar: #ffd400; }  /* yellow                     */
.vol-hiking  { --bar: #c9d92e; }  /* its relative, towards olive */
:root[data-theme="light"] .vol-walking { --bar: #8a6a00; }
:root[data-theme="light"] .vol-hiking  { --bar: #5f6b00; }

.volume-fill[class*="vol-c"] {
  background: linear-gradient(180deg, var(--bar),
                              color-mix(in srgb, var(--bar) 35%, transparent));
  box-shadow: 0 0 14px color-mix(in srgb, var(--bar) 25%, transparent);
}

/*
  The figures under a split week: one block per discipline, its swatch tying the number to its bar.

  Two lines per discipline, deliberately. Five weeks across a 390px phone is about sixty pixels a
  column and the percentage beside the amount wants ninety, so on one line the browser broke the
  amount itself: "14" and "km" on separate lines, directly above the next discipline's swatch, four
  fragments with nothing saying which belonged together. Percentage on top with the swatch, absolute
  amount under it, and the amount never breaks.
*/
.volume-split-figures { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.volume-split-row { display: flex; flex-direction: column; align-items: center; }
.volume-split-head { display: flex; align-items: center; gap: 4px; }
/* Smaller than a single week's headline figure: there are two or three of them in the width one
   used to have, and they are being compared with each other rather than read from across a room. */
.volume-split-row .volume-value { font-size: 12.5px; }

.volume-swatch {
  width: 7px;
  height: 7px;
  border-radius: 2px;
  flex: 0 0 auto;
  background: var(--bar, var(--accent));
}

/* Which colour is which discipline, once, under the chart. Without it the swatches tie a figure to
   a bar and nothing says which bar is running. */
.volume-legend {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  padding: 0;
  margin: 12px 0 0;
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--mut2);
}
.volume-legend li { display: flex; align-items: center; gap: 5px; }

/* A single week has no other week to be compared against, so a bar spanning the
   whole card reads as "a lot" when it only means "the only one". Cap the width. */
.volume-week:only-child { max-width: 96px; }

/*
  Bar sizes, in 5% steps.
  These exist because a Content-Security-Policy of style-src 'self' drops style
  attributes, so `style="height:70%"` written by the template is silently
  ignored. Twenty-one classes cost about 700 bytes and keep the policy intact.
  Height for the vertical volume bars, width for the horizontal HR-zone bars -
  one set of names serves both.
*/
.bar-0   { height: 0;     width: 0; }
.bar-5   { height: 5%;    width: 5%; }
.bar-10  { height: 10%;   width: 10%; }
.bar-15  { height: 15%;   width: 15%; }
.bar-20  { height: 20%;   width: 20%; }
.bar-25  { height: 25%;   width: 25%; }
.bar-30  { height: 30%;   width: 30%; }
.bar-35  { height: 35%;   width: 35%; }
.bar-40  { height: 40%;   width: 40%; }
.bar-45  { height: 45%;   width: 45%; }
.bar-50  { height: 50%;   width: 50%; }
.bar-55  { height: 55%;   width: 55%; }
.bar-60  { height: 60%;   width: 60%; }
.bar-65  { height: 65%;   width: 65%; }
.bar-70  { height: 70%;   width: 70%; }
.bar-75  { height: 75%;   width: 75%; }
.bar-80  { height: 80%;   width: 80%; }
.bar-85  { height: 85%;   width: 85%; }
.bar-90  { height: 90%;   width: 90%; }
.bar-95  { height: 95%;   width: 95%; }
.bar-100 { height: 100%;  width: 100%; }

/* The volume bar is sized by height; its width always fills the column. */
.volume-fill[class*="bar-"] { width: 100%; }
/* The zone bar is sized by width; its height comes from the track. */
.zone-fill[class*="bar-"] { height: 100%; }
.volume-label { font-family: var(--cond); color: var(--mut2); font-size: 12px; letter-spacing: 1px; margin-top: 8px; }
/* The effort figure is the headline now, so it reads as one - the kilometres under it are the
   working, kept because a bar labelled only "82%" cannot be checked against anything. */
.volume-value { font-family: var(--cond); color: var(--ink-2); font-size: 14px; letter-spacing: 1px; }
/* nowrap: "14 km" is one quantity and a column narrow enough to split it is narrow enough to let
   the figure overhang instead. */
.volume-km { font-family: var(--cond); font-size: 12px; letter-spacing: 1px; white-space: nowrap; }
.volume-basis { margin: 12px 0 0; font-size: 12.5px; }

/* The per-session view, folded away inside the same card: same question, finer resolution. */
.session-bars { margin-top: 14px; border-top: 1px solid var(--line-soft); padding-top: 10px; }
/*
  Rimossa: .session-bars .volume-bars { height: 120px }.

  Era un'altezza sul contenitore mentre l'altezza vera sta sulla barra, e le due si sono
  contraddette appena la barra e' passata a 140: colonne di 140 dentro una scatola di 120, quindi
  venti pixel che uscivano dall'alto e finivano sopra "SEDUTA PER SEDUTA". Segnalato guardandolo -
  "il comando sotto e' coperto dalle colonne". Adesso l'altezza la decide solo la barra e il
  contenitore prende quello che serve.
*/

/* A set that was never ticked, on a finished workout. Dimmed and labelled rather than carrying
   figures: the logged columns are seeded from the plan, so printing them made an untouched set
   look like one performed exactly to plan. */
.session-set.is-skipped { opacity: .55; }
.set-skipped {
  font-family: var(--cond);
  letter-spacing: 1.2px;
  font-size: 12px;
  color: var(--mut2);
  text-transform: uppercase;
}


/* "estimate", on the expenditure tile. Quiet, but present on the number itself rather than only in
   the note below - somebody reading the tile alone must still see it.

   Called .tile-flag and not .tile-warn, which is what it was. That name already belonged to the
   modifier forty lines above - the border and glow a tile gets when the week is spiking - and this
   rule, being later, won. So on exactly the weeks the warning was meant for, the "this week"
   tile stopped being a tile: it took display:inline-block, border-radius:999px, 9px type and 1px
   of padding, shrank to fit its own text and rendered as a pill. Two unrelated things, one class
   name, and the collision only showed above 150%. */
.tile-flag {
  display: inline-block;
  margin-top: 4px;
  padding: 1px 7px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-family: var(--cond);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--mut2);
}
/* One line per discipline under the percentage: "1 / 3 palestra", "7,5 / 12 km corsa". The
   percentage says how the week is going; these say which half of it is behind. */
/*
  ---------- the verdict, then "Espandi" ----------

  The overall reading sits above the figures it is about, so it shows its verdict and keeps the
  reasoning one tap away.

  The clamp is on the first paragraph, not on the block that holds them all. Clamping the block was
  the obvious version and it did not work: the coach's reading is a verdict, a blank line and then
  the reasoning, so what the athlete got was two lines of verdict, a gap, and the reasoning cut
  in the middle of a word - four lines, measured, where three were asked for. -webkit-line-clamp
  counts line boxes and the paragraph margin between them is not one.

  So the fold shows one thing: the sentence the prompt asks the coach to make able to stand alone.
  Three lines is its ceiling rather than its target - it is usually one or two - and the reasoning
  is what the control reveals. Folded, that reasoning is hidden from assistive technology as well as
  from the screen, which is what a disclosure is: the control is announced, and opening it is one
  press for everybody.

  :has sul contenitore perche' il comando viene dopo il testo che governa - che e' il giusto ordine
  di lettura e l'ordine sbagliato per un ~ semplice. Sul .box-comment: e' il contenitore che tiene
  insieme il consiglio e la riga per rispondere, e ogni scheda ha il suo, quindi la regola resta
  chiusa dentro una scheda sola anche con cinque commenti sulla stessa pagina.
*/
.advice-fold .ai-feedback-body > * { display: none; }
.advice-fold .ai-feedback-body > :first-child {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
  margin: 0;
}
.box-comment:has(.advice-more[open]) .advice-fold .ai-feedback-body > * { display: block; }
.box-comment:has(.advice-more[open]) .advice-fold .ai-feedback-body > :first-child {
  -webkit-line-clamp: unset;
  overflow: visible;
}
/* Paragraphs of advice, told apart. The reset zeroes every margin in the document, so the coach's
   verdict and the reasoning under it were printed as one wall of text - which for the reading under
   the ring is the difference between a sentence anyone can take in and a paragraph nobody reads.
   Between paragraphs only: nothing above the first one, which would push the text off its own
   card. */
.ai-feedback-body > p + p { margin-top: 10px; }
/* Left, inside a centred hero. The ring, its state word and the "Koræi dice" label are centred and
   should be; a paragraph of six lines is text to read, and centred text to read has a ragged left
   edge for the eye to find on every line. */
.readiness .ai-feedback-body { text-align: left; }

/*
  ---------- the coach's block keeps its width, open or closed ----------

  The hero is a centred flex column, so every child of it is sized to its own content: the reading
  was as wide as its widest line. Folded that is one sentence and the box drew itself narrow; expanded
  it is a paragraph and the box filled the card. Same with the ask line under it - the input and its
  send button moved outwards as the text above them grew.

  So the block was changing shape rather than only its height, and the athlete read that as the card
  being broken. Stretching it takes the width off the content: one width, decided by the card, in both
  states. It is the block that stretches rather than the box inside it, because the ask line and the
  timestamp belong to the same column and would otherwise still swing.
*/
.readiness .box-comment,
.readiness .readiness-note { align-self: stretch; }
/* One label at a time: the control says what pressing it will do. */
.advice-more .advice-more-close,
.advice-more[open] .advice-more-open { display: none; }
.advice-more[open] .advice-more-close { display: inline; }

.week-counters { display: flex; flex-direction: column; gap: 2px; margin-top: 4px; }
.week-counter { display: block; font-size: 12px; color: var(--mut); font-variant-numeric: tabular-nums; }
/* "e altre 4": lo stesso controllo della lista dei pasti, in una casella larga meta' schermo.
   Piu' piccolo di quello - qui accanto sta una riga da dodici pixel, non una lista - e comunque
   alto 44 perche' e' una cosa da toccare con un dito. */
.week-more > summary {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  cursor: pointer;
  list-style: none;
  font-family: var(--cond);
  font-size: 11.5px;
  letter-spacing: 1.1px;
  text-transform: uppercase;
  color: var(--accent-text);
}
.week-more > summary::-webkit-details-marker { display: none; }
.week-more > summary:hover { color: var(--accent); }
.week-more[open] > summary .session-detail-mark { transform: rotate(-135deg) translateY(3px); }
.week-more .week-more-close,
.week-more[open] .week-more-open { display: none; }
.week-more[open] .week-more-close { display: inline; }
.week-more > .week-counter { padding-left: 0; }

.load-estimate-note { margin-top: 10px; font-size: 13px; }
.load-estimate-note a { margin-left: 6px; }

/* The last-session card opens onto the recent list. A summary styled like the section links
   elsewhere, and the button to the full history at the bottom of it. */
/*
  ---------- the recent list, and the fact that it opens ----------

  It was an uppercase line in muted type with no marker, and the athlete read it as a heading. Fair:
  nothing about it said there was anything behind it, and a disclosure nobody can see is a disclosure
  nobody opens.

  Three things fix it, and they are the same three the rest of the interface already uses: the chevron
  from the ask fold, drawn in text so it needs no asset and turns with the state; the accent colour,
  which in this interface means "you can press this"; and a label that counts what it hides.
*/
.last-session-more { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line-soft); }
.last-session-more > summary {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  font-family: var(--cond);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--accent-text);
  cursor: pointer;
  list-style: none;
}
.last-session-more > summary::-webkit-details-marker { display: none; }
.last-session-more > summary::before {
  content: "\203A";
  display: inline-block;
  font-size: 17px;
  line-height: 1;
  transition: transform .15s ease;
}
.last-session-more[open] > summary::before { transform: rotate(90deg); }
.last-session-more > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
  .last-session-more > summary::before { transition: none; }
}

/*
  ---------- one block per discipline ----------

  The newest workout sits at the top of the card on its own; every other discipline is inside the
  fold, and each one needs a line saying where the previous one ended. A rule and space rather than
  a nested card: they are the same kind of thing at the same level, and boxing each one would put
  two borders round every set of figures.
*/
/*
  Una sessione dall'altra: un filo e un po' d'aria.

  La regola valeva solo dentro alla piega, e fuori le sessioni dello stesso giorno si toccavano: il
  titolo della seconda partiva attaccato alle caselle della prima, e la colonna del marchio e di
  "elimina" - che sta in alto a destra - finiva appiccicata alla scheda sopra. "Sistema la
  spaziatura, non deve sovrapporsi alla scheda sopra": due allenamenti nello stesso pomeriggio sono
  il caso normale, non quello raro.

  Il selettore fratello copre quel caso, quello con la piega copre il primo blocco dentro di essa -
  che fratello di un blocco non e'. Insieme dicono una regola sola: fra due sessioni ci va sempre lo
  stesso stacco, ovunque siano disegnate.
*/
.last-session-block + .last-session-block,
.last-session-more .last-session-block {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--line-soft);
}
/* The eyebrow belongs to the card, not to the block, so the first block's own heading sits directly
   under it without the gap a second card head would carry. */
.last-session > .card-eyebrow {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 8px;
  margin-bottom: 2px;
}
/* Which day the workouts under it are from. In the ink colour, not the eyebrow's muted grey: the
   eyebrow labels the card and this is a fact about the training, so it has to outrank the label
   beside it rather than read as more of it. Also on the fold's summary, where it says what the
   count is other than. */
.last-session-day {
  font-family: var(--cond);
  letter-spacing: 1.2px;
  color: var(--ink-2);
  text-transform: none;
}
.last-session-more > summary .last-session-day { color: inherit; opacity: .75; }
.last-session-more .btn { margin-top: 10px; }

/* The chat page gives the thread the screen. On the dashboard it had whatever was left under a bar
   chart, which is not a place to read six sentences. */

/*
  ---------- the chat gets the screen ----------

  Measured at 390x844 before this rule: the coach's text was 228px wide. Twenty-six characters a
  line, on a 390px phone - 58% of the display - and nobody can read six sentences of coaching in a
  column that narrow.

  Nothing was wrong with any single rule. It was four paddings stacked: the shell's 16 a side, the
  card's 20 a side, the bubble's max-width of 84%, and then the bubble's own 16 a side. Each is
  reasonable for a dashboard tile beside eleven others; on the one page whose entire content is
  prose, together they left the prose a third of the screen.

  So the chat page - and only the chat page - undoes them. The shell narrows its gutter, the card
  pulls out past what is left of it, and the bubbles take almost the full width of what remains.
  The card keeps its border: it is still recognisably the same surface as everywhere else, just not
  holding the text away from the edge.

  Wider than 520px on a desktop rather than "the whole window", and that is the "or nearly" part.
  Prose is read fastest at roughly 60-80 characters a line; 860px of shell works out at about 80,
  and a chat stretched across a 27-inch monitor would be harder to read than the phone was.
*/
.chat-shell { max-width: 860px; }
/* The card's own heading, promoted to the page's h1 now the wordmark above it is gone. Sized like
   the h2 it replaced, because nothing about the page changed except which tag carries the name. */
.chat-heading { font-size: 24px; font-weight: 800; letter-spacing: -.2px; }
/* The bar has to move with it or the wordmark sits in the middle of the page above content that
   starts at the edge. :has on the body because nav.top is the layout's, outside this main. */
body:has(.chat-shell) nav.top { max-width: 860px; }

/* 88%, not 84%: at this width the difference is 30 characters a line, and the bubbles still read
   as bubbles - the gap on the far side is what says who is speaking. */
.chat-page .bubble { max-width: 88%; }

@media (max-width: 560px) {
  /*
    A margin either side, restored on report: the card ran to within four pixels of the screen
    edge and the text looked like it was falling off it.

    The pull-out this replaces existed to win back characters per line - the measurement that
    started it was 228px of text on a 390px phone, which is unreadable. That is still true, so the
    gutter is 12px rather than the 16 the rest of the application uses, and the card no longer
    reaches back out past it.

    Measured at 390px afterwards: a coach's bubble is 326px wide with 300px of text in it. The
    pull-out gave 321px of text, so a visible margin on both sides costs twenty-one pixels of line -
    about three characters - and the unreadable version is still seventy pixels behind.
  */
  .chat-shell { padding-left: 12px; padding-right: 12px; }
  .chat-shell > .chat-page { padding-left: 12px; padding-right: 12px; }
  .chat-page .bubble { max-width: 96%; padding: 12px 13px; }
}

/*
  "Back to the latest", floating over the bottom of the screen.

  Hidden while the page is already at the bottom, which is most of the time - a control that is
  always there is a control that is in the way. app.js unhides it once the athlete has scrolled up
  far enough for the newest message to be off screen.

  Fixed to the viewport rather than absolute inside the thread, which is what it was when the
  thread had a bottom edge of its own. The thread is now as tall as the conversation, so its
  bottom-right corner is wherever the last message happens to fall - which on a long thread is
  several screens below the athlete. Lifted clear of the floating bottom bar.
*/
.chat-jump {
  position: fixed;
  right: 14px;
  bottom: calc(var(--bar-h) + 28px);
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 1px solid var(--accent-line);
  background: var(--accent);
  color: var(--on-accent);
  font-size: 17px;
  line-height: 1;
  box-shadow: var(--glow-accent-soft);
  z-index: 2;
}
.chat-jump[hidden] { display: none; }

/* "Show earlier messages", centred at the top of the thread. Scrolls away with the conversation
   rather than floating: it is a boundary marker, and its place is where the history stops. */
.thread-earlier { display: flex; justify-content: center; margin: 0 0 4px; }
.chat-jump:hover { filter: brightness(1.06); }

/* ---------- the coach under each box ----------
   Advice, the day's questions about it, and the line the next one is typed into. Quiet throughout:
   it is a footnote to the numbers above, not a second heading competing with them. */
/*
  Il blocco del coach sotto una scheda: avviso, bottoni, attesa, consiglio.

  In colonna con un distacco, e la ragione e' il reset: "* { margin: 0 }" azzera i margini di tutto
  il documento, quindi un avviso, il bottone "riprova" e quello che chiede il parere si toccavano -
  tre comandi diversi che sembravano un blocco solo. "Troppo attaccato, stacca."

  Il gap e non un margine sui figli: i figli sono di quattro tipi diversi e compaiono a due a due a
  seconda di cosa e' successo, quindi qualunque margine scritto su uno di loro lascerebbe scoperta
  una delle combinazioni. La spaziatura appartiene alla colonna, non a chi ci sta dentro.

  Niente align-items: i figli devono restare larghi quanto la carta - il riquadro del consiglio per
  primo - e sono i bottoni dentro ai moduli a essere larghi quanto il loro testo.
*/
.box-comment {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--line-soft);
}
.box-comment .ai-feedback { margin: 0 0 4px; }
.box-comment-wait { font-size: 13px; margin: 0; }
/* When the advice was written. Small, under it: advice is rewritten when something happens rather
   than every day, so a sentence from Tuesday can be correct on Thursday and must say so. */
/* L'ora sta in basso a destra del messaggio, sotto il controllo che lo apre.

   Era centrata, e non per scelta: e' un blocco dentro l'eroe della home, che e' una colonna
   centrata, e l'allineamento del genitore le passava attraverso - lo stesso motivo per cui
   l'"espandi" sembrava messo a caso. text-align: right invece di margin-left: auto perche' e' un
   blocco largo quanto la carta: sono le sue parole a dover andare a destra, non la sua scatola. */
.box-advice-when {
  display: block;
  margin-top: 6px;
  font-size: 12px;
  text-align: right;
}

/*
  ---------- removed: the exchange and the ask line ----------

  Rules for a folded question box under every card and for the one answer it kept. Both are gone
  from the markup: the home had five ways to talk to the coach and now has one, .box-chat-ask,
  which goes to the chat instead of answering in place. What is left of a box is the advice above.
*/

/* The dashboard's notes card: one note, dated, and how many others there are. The whole list, with
   its edit forms, is the subject of /memory - here it was most of the page. The card is itself the
   link, so it needs the anchor reset a card does not normally carry. */
a.memory-card { display: block; color: inherit; }
a.memory-card:hover { text-decoration: none; border-color: var(--accent-line); }
a.memory-card:hover .memory-more { color: var(--accent-text); }
.memory-more { font-family: var(--cond); font-size: 12.5px; letter-spacing: 1.2px; color: var(--mut); }
.memory-when { display: block; margin-top: 3px; font-size: 12px; }

/* ---------- profile picture ---------- */
.avatar-row { display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-start; margin: 14px 0 4px; }
.avatar {
  flex: 0 0 auto;
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid var(--accent-line);
  background: var(--panel-2, var(--panel));
}
.avatar-lg { width: 96px; height: 96px; }
/* No picture: the first letter of the name, which is better than a grey silhouette and needs no
   image to load. aria-hidden, because the name is already on the page. */
.avatar-initial {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--cond);
  font-size: 38px;
  font-weight: 700;
  color: var(--accent-text);
}
.avatar-actions { flex: 1 1 200px; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.avatar-actions form { margin: 0; }
.avatar-form { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.avatar-form label { margin: 0; cursor: pointer; }
/* The input is driven by the label above it, and parked off-screen rather than hidden so it stays
   in the tab order - the same reasoning as the drawer's checkbox. */
.avatar-input { position: absolute; left: -9999px; }
.avatar-hint { flex: 1 1 100%; margin: 0; font-size: 12px; }

/* ---------- coach tone ---------- */
.tone-group { border: none; margin: 0 0 18px; padding: 0; }
.tone-group legend {
  padding: 0;
  font-family: var(--cond);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--mut2);
}
/* Each option is the whole row, so there is no small radio to aim at on a phone. */
.tone {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 4px 12px;
  align-items: baseline;
  margin-top: 8px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 12px;
  cursor: pointer;
}
.tone:hover { border-color: var(--accent-line); }
.tone:has(input:checked) { border-color: var(--accent-line); background: var(--accent-soft); }
.tone input { margin: 0; grid-row: span 2; }
.tone-name {
  font-family: var(--cond);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
}
.tone:has(input:checked) .tone-name { color: var(--accent-text); }
.tone-what { grid-column: 2; font-size: 13px; color: var(--mut); }

/* ---------- notes the athlete writes ----------

   The edit form is a <details> so the list is not a wall of textareas, and the add form is pushed
   below the list behind a rule: reading what is already remembered comes before writing more. */
.memory-edit { margin: 6px 0 2px; }
.memory-edit > summary {
  display: inline-block;
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--mut2);
  cursor: pointer;
}
.memory-edit > summary:hover { color: var(--accent-text); }
.memory-edit textarea { margin: 8px 0; }
.memory-add { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--line-soft); }


/* ---------- activity detail ---------- */
.detail-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.metric-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 18px; }
.metric {
  background: var(--field);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-sm);
  padding: 13px 15px;
}
.metric-value {
  display: block;
  font-size: 23px;
  font-weight: 800;
  margin-top: 5px;
  letter-spacing: -.3px;
}
.chart-box { position: relative; height: 210px; margin-top: 18px; }
.chart-hint { font-size: 12.5px; margin-top: 12px; }

.zone-list { list-style: none; padding: 0; margin: 16px 0 0; }
.zone { display: grid; grid-template-columns: 1fr auto; gap: 5px 10px; align-items: center; margin-bottom: 14px; }
.zone-name { font-family: var(--cond); font-size: 14.5px; letter-spacing: 1.4px; text-transform: uppercase; }
.zone-range { color: var(--mut); font-size: 12.5px; }
.zone-bar { grid-column: 1 / 2; height: 9px; border-radius: 999px; background: var(--line-soft); overflow: hidden; }
.zone-fill {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 35%, transparent), var(--accent));
  box-shadow: 0 0 12px color-mix(in srgb, var(--accent) 35%, transparent);
}
.zone-pct { font-size: 13px; color: var(--mut); }

/* ---------- import ---------- */
.job-list { list-style: none; padding: 0; margin: 0; }
.job { border-bottom: 1px solid var(--line-soft); padding: 13px 0; }
.job:last-child { border-bottom: none; }
.job-main { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.job-name { word-break: break-all; font-size: 14.5px; }
.job-error { font-size: 13px; margin: 6px 0 0; }
.job-link { font-size: 13.5px; }

.badge {
  flex: 0 0 auto;
  border-radius: 999px;
  padding: 5px 13px;
  border: 1px solid var(--line);
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: var(--mut);
}
.badge-pending { color: var(--accent-text); border-color: var(--accent-line); background: var(--accent-soft); }
.badge-failed { color: var(--danger); border-color: rgba(255, 92, 92, .4); }

.upload-results { list-style: none; padding: 0; margin: 0 0 16px; font-size: 14px; }
.upload-results li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 7px 0;
}

/* ---------- chat ---------- */
.thread { display: flex; flex-direction: column; gap: 12px; margin: 18px 0; }
.bubble {
  border: 1px solid var(--line-soft);
  border-radius: 18px;
  padding: 13px 16px;
  max-width: 84%;
  background: var(--card2);
}
.bubble-coach {
  align-self: flex-start;
  background: var(--accent-soft);
  border-color: color-mix(in srgb, var(--accent) 26%, transparent);
}
/*
  The athlete's own messages sit on the right, the coach's on the left, which is
  what every messaging interface has taught people to read.

  .chat-day has to be a flex column for that to work. Grouping the thread by day
  put the bubbles inside a plain <div>, so align-self had no flex parent to align
  against and every bubble silently went back to the left - a layout regression
  introduced by a change that had nothing to do with layout.
*/
.chat-day { display: flex; flex-direction: column; gap: 12px; }
.bubble-athlete {
  align-self: flex-end;
  background: var(--field);
  border-color: var(--line);
}
/* The label reads right-to-left on a right-hand bubble; the text stays
   left-aligned, because centred or right-aligned prose is harder to read. */
.bubble-athlete .bubble-who { justify-content: flex-end; }
.bubble-who { margin-bottom: 5px; letter-spacing: 2px; font-size: 12px; }
.bubble-coach .bubble-who { color: var(--accent-text); }
.bubble-text { font-size: 16.5px; color: var(--ink-2); }
.bubble-text p { margin: 0 0 8px; }
.bubble-text p:last-child { margin-bottom: 0; }
.bubble-text ul, .bubble-text ol { margin: 6px 0; padding-left: 20px; }
/*
  ---------- una risposta con dentro delle sezioni ----------

  Le risposte del coach erano prosa e basta: il prompt vietava titoli ed elenchi. Ora li chiede -
  "le interazioni e la qualita' delle risposte devono essere simili a questa come approfondimenti,
  lunghezza e utilita'" - quindi la bolla deve saperli disegnare, altrimenti arrivano e sembrano
  testo qualunque.

  h4 e non h3: il ripulitore consente h4, h5 e h6 e butta via gli altri, quindi un "###" del modello
  perderebbe il tag e resterebbe una riga di testo indistinguibile. Il prompt chiede quattro cancelli
  per questo, e queste due regole sono l'altra meta' dello stesso accordo.

  Piu' aria sopra che sotto: un titolo appartiene a quello che introduce, non a quello che lo
  precede, e la distanza e' come si vede senza leggere.
*/
.bubble-text h4, .bubble-text h5, .bubble-text h6 {
  margin: 18px 0 6px;
  font-family: var(--cond);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: .6px;
  color: var(--ink);
}
.bubble-text > h4:first-child { margin-top: 0; }
.bubble-text li { margin: 0 0 5px; }
.bubble-text li:last-child { margin-bottom: 0; }
/* L'etichetta in neretto che apre ogni punto: e' quella che rende scorribile un elenco di cinque. */
.bubble-text strong { color: var(--ink); font-weight: 700; }
.bubble-text code {
  background: var(--track);
  padding: 1px 6px;
  border-radius: 6px;
  font-size: 13.5px;
}

/*
  I collegamenti rapidi sotto a una risposta del coach.

  Sono la seconda meta' di quello che il coach dice quando l'atleta annuncia una decisione - "voglio
  perdere 4kg" - e vanno letti come parte della risposta, non come una barra di navigazione: quindi
  dentro alla bolla, sotto al testo, staccati da un filo.

  Pastiglie con il bordo e non bottoni pieni: il bottone pieno di una scheda e' quello che comincia
  qualcosa e su questa pagina non ce ne sono, ma due bottoni accesi sotto a ogni risposta sarebbero
  la cosa piu' rumorosa del filo. Vanno a capo da soli, perche' "piano di allenamento AI" accanto a
  "scheda alimentare" su un telefono da 390 pixel non ci sta.

  Alti 36 e non 44: la riga di sotto e' un'altra pastiglia e non del testo, quindi il dito non ha
  niente di sbagliato da colpire qui intorno, e 44 sotto a ogni risposta allungherebbe il filo.
*/
.bubble-links {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--line-soft);
}
.bubble-link {
  display: inline;
  color: var(--accent-text);
  font: inherit;
  font-size: 0.9rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  text-transform: none;
  text-decoration: underline;
  text-decoration-color: var(--accent-line);
  text-underline-offset: 3px;
}
.bubble-link:hover {
  color: var(--accent);
  text-decoration-color: currentColor;
}
.bubble-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* A pending coach write has two unambiguous choices, kept beside the proposal. */
.chat-action-choices {
  display: flex;
  gap: 10px;
  width: min(100%, 280px);
  margin: 12px 0 4px;
}
.chat-action-choices .btn {
  flex: 1 1 0;
  width: auto;
  margin: 0;
}

/*
  scroll-margin-bottom, and it is load-bearing rather than cosmetic.

  app.js brings the conversation's end into view by scrolling this form to the bottom of the
  viewport - the page's own bottom is past the recap cards, several screens below the newest
  message. Without a margin the browser tucks the form exactly against the bottom edge, which is
  where the floating bottom bar sits, so the input the athlete was sent to would be underneath it.
*/
/*
  Il posto dove si scrive, largo quanto il messaggio qui sopra.

  Era una riga di tre cose affiancate: un campo stretto, la macchina fotografica e una pastiglia
  con scritto INVIA. Su un telefono il campo restava largo la meta' della bolla che gli stava sopra,
  e la parola INVIA si prendeva il quarto di riga che mancava al campo.

  Adesso e' una scatola sola, larga quanto la conversazione: il testo occupa tutta la prima riga e i
  due comandi stanno nell'angolo in basso a destra, dove li mette qualunque applicazione di messaggi.
  Niente da imparare e niente da leggere.
*/
.chat-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 16px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 20px;
  background: var(--field);
  scroll-margin-bottom: calc(var(--bar-h) + 24px);
}
/* Tutta la prima riga per se': e' il pezzo che si legge mentre si scrive. */
.chat-form #chat-input {
  flex: 1 1 100%;
  margin: 0;
  padding: 6px 4px;
  border: none;
  background: none;
  border-radius: 0;
  resize: none;
  overflow-y: auto;
  /* Due righe esatte e non una di piu': oltre, la conversazione sparisce sotto a cio' che si sta
     scrivendo. app.js alza la scatola fino a qui e poi la lascia scorrere.
     3.8em = due interlinee (1.5 ciascuna) piu' i 12 pixel di spaziatura sopra e sotto, che a 15px
     fanno 0.8em. Misurato: vuota 35, due righe 57, e da li' non sale piu'. */
  max-height: 3.8em;
  line-height: 1.5;
}
.chat-form #chat-input:focus-visible { outline: none; }
/* Il fuoco si vede sulla scatola intera, non sul campo che dentro non ha bordi suoi. */
.chat-form:focus-within { border-color: var(--accent-line); }
/* I due comandi, in fondo a destra. */
.chat-form .chat-clip { margin-left: auto; }
.chat-send {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin: 0;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--accent-line);
  background: var(--accent);
  color: var(--on-accent);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
/* Nessun alone qui. L'alone e' il segno di "questa e' la cosa da premere" e sulla pagina ne esiste
   uno solo per volta: il bottone pieno di una scheda, e quello che riporta in fondo alla chat.
   Questo e' gia' pieno, tondo e nel colore dell'accento - si vede senza. Alla pressione stringe
   appena, come ogni altro bottone del foglio. */
.chat-send:hover { filter: brightness(1.08); }
.chat-send:active { transform: scale(.94); }

/*
  ---------- the clip in the chat composer ----------

  A <label> holding a hidden file input, because a file input cannot be styled into a 44px round
  control and this row is already three items wide on a 390px screen. The label IS the button: a click
  anywhere on it opens the sheet, which on a phone offers the camera and the library together.

  The input is moved out of sight rather than display:none - a hidden input is still focusable and
  still announced, and display:none takes it out of the tab order entirely.
*/
.chat-clip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  margin: 0;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--field);
  color: var(--mut);
  cursor: pointer;
}
.chat-clip:hover, .chat-clip:focus-within { color: var(--accent-text); border-color: var(--accent-text); }
.chat-clip input[type="file"] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
/*
  ---------- a photograph offered as a note ----------

  Under the note box, because it fills it: the reading comes back with the sentence already in the
  field above and the athlete presses the button that was already there. Bordered off from the form
  above it so the two are visibly separate actions rather than one long form.
*/
.memory-photo {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--line-soft);
}
.memory-photo input[type="file"] { margin-top: 8px; }
.memory-photo-hint { font-size: 12.5px; margin-top: 8px; }
.memory-photo .btn { margin-top: 12px; }
.memory-photo-reading { margin-top: 14px; }
.memory-photo-nothing { font-size: 12.5px; margin-top: 8px; }
.htmx-indicator { display: none; margin-top: 12px; font-size: 13.5px; }
.htmx-request .htmx-indicator, .htmx-request.htmx-indicator { display: block; }

/*
  ---------- the waits that are long enough to be worth drawing ----------

  Four animations, one per wait that genuinely takes seconds: the coach writing, a meal photo being
  read, a schedule photo being read, a finished session being analysed. Everything else in the
  application that shows a pending state is a swap of a fifth of a second, and it keeps its plain
  sentence - an animation there would advertise a wait nobody had noticed, which makes the app feel
  slower rather than friendlier.

  Themed rather than generic on purpose. Shoes while the coach reads your running, a barbell while
  it reads your gym, a fork while it reads your lunch: the animation is saying what is happening,
  which is the difference between craft and decoration.

  All motion is here, none of it is JavaScript, and there is not a byte of network in it - the
  content security policy allows no inline script or style, and these need neither.
*/
/*
  A label for a screen reader and for nobody else.

  It was already being used - the chat input's label carries `class="sr-only"` - and the class had
  never been written. So the label was fully visible above the composer, in the styling of a form
  label, reading "??chat.label_it??" because the message key did not exist either. Two halves of
  the same omission, on the page people use most.

  Not display:none and not visibility:hidden: both remove the element from the accessibility tree,
  which is exactly the thing this class exists to keep. Same technique as the hidden file inputs
  above, for the same reason.
*/
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/*
  ---------- when a wait is on screen, and when it is not ----------

  Three rules, and the order of the middle one is the whole thing.

  The first version had only `.waiting-wrap { display: flex }`. It sits below
  `.htmx-indicator { display: none }` in this file and has exactly the same specificity - one class
  each - so the later rule won and every indicator was permanently visible. The nutrition card said
  "sto stimando" with a fork animating beside it on a page where nothing had been uploaded; the
  chat said "Koræi sta scrivendo" with nobody writing. An animation that is always running is not a
  loading state, it is a decoration that lies.

  So the hidden state is restated at two classes, which beats the flex, and the shown state at
  three, which beats them both. Specificity all the way up rather than an !important, because the
  next person adding a state to this element needs somewhere above it to stand.
*/
.waiting-wrap { display: flex; align-items: center; gap: 10px; }
/* An indicator is hidden until HTMX says otherwise, and .waiting-wrap must not undo that. */
.htmx-indicator.waiting-wrap { display: none; }
/* In flight: flex, so the animation and its sentence sit on one line. */
.htmx-request .waiting-wrap.htmx-indicator,
.htmx-request.waiting-wrap.htmx-indicator { display: flex; }
.waiting {
  width: 52px;
  height: 26px;
  flex: 0 0 auto;
  overflow: visible;
  color: var(--accent-text);
}
.waiting-label { font-size: 13.5px; color: var(--mut); }

/* The stride. Each shoe lifts and settles, half a cycle apart, so the pair reads as running
   rather than as two things bouncing together. */
.waiting-shoes .shoe { animation: shoe-step 620ms ease-in-out infinite; }
.waiting-shoes .shoe-front { animation-delay: 310ms; }
.waiting-shoes .ground { opacity: .35; animation: ground-pass 620ms linear infinite; }
.waiting-shoes .shoe-front .ground { animation-delay: 310ms; }
@keyframes shoe-step {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  35% { transform: translateY(-5px) rotate(-8deg); }
  70% { transform: translateY(-1px) rotate(2deg); }
}
@keyframes ground-pass {
  0% { opacity: .05; transform: translateX(3px); }
  50% { opacity: .4; }
  100% { opacity: .05; transform: translateX(-3px); }
}

/* The press. The bar travels and the plates flex a little at the ends, which is what a loaded bar
   does - it is what makes the weight legible without drawing somebody holding it. */
.waiting-barbell .bar { animation: bar-press 1.15s ease-in-out infinite; }
.waiting-barbell .plate { animation: plate-flex 1.15s ease-in-out infinite; }
.waiting-barbell .rack { opacity: .28; }
@keyframes bar-press {
  0%, 100% { transform: translateY(3px); }
  45% { transform: translateY(-4px); }
}
@keyframes plate-flex {
  0%, 100% { transform: scaleY(1); }
  45% { transform: scaleY(.88); }
}

/* The fork moves and the plate does not: the food is the subject, the reading is the activity. */
.waiting-fork .fork { animation: fork-dip 1.1s ease-in-out infinite; transform-origin: 52px 6px; }
.waiting-fork .plate-rim { opacity: .5; }
.waiting-fork .plate-inner { opacity: .25; animation: plate-pulse 1.8s ease-in-out infinite; }
@keyframes fork-dip {
  0%, 100% { transform: translate(0, 0) rotate(0deg); }
  40% { transform: translate(-13px, 4px) rotate(-14deg); }
  60% { transform: translate(-13px, 2px) rotate(-14deg); }
}
@keyframes plate-pulse {
  0%, 100% { opacity: .18; }
  50% { opacity: .4; }
}

/* The fallback, for a wait with no subject of its own. Quietest of the four by design. */
.waiting-dots .dot { animation: dot-rise 1.05s ease-in-out infinite; }
.waiting-dots .dot-2 { animation-delay: 150ms; }
.waiting-dots .dot-3 { animation-delay: 300ms; }
@keyframes dot-rise {
  0%, 100% { transform: translateY(0); opacity: .35; }
  40% { transform: translateY(-5px); opacity: 1; }
}

/*
  Motion off, animation still there.

  Not display:none. Somebody who has asked their system for less movement still needs to know the
  coach is working, and the shape beside the sentence is what says so - it just stops moving. The
  wording next to it carries the meaning either way, which is why none of these is the only thing
  telling the athlete anything.
*/
@media (prefers-reduced-motion: reduce) {
  .waiting *, .waiting { animation: none !important; }
}

/* A request in flight has to be visible even where no indicator element exists.
   Without this, pressing Remove on a schedule did nothing at all for as long as the
   round trip took: no spinner, no dimming, no disabled button. On a phone that reads
   as a dead button, so the athlete pressed another one - and the first swap landing a
   moment later looked like the second press had caused it. The row was never late;
   there was simply nothing on screen saying anything was happening. */
form.htmx-request { opacity: .55; }
form.htmx-request button,
button.htmx-request { cursor: progress; }
/* Pressed and waiting: the button keeps its size (no layout jump) and pulses. */
form.htmx-request button, button.htmx-request { animation: pending 1s ease-in-out infinite; }
@keyframes pending { 50% { opacity: .45; } }
@media (prefers-reduced-motion: reduce) {
  form.htmx-request button, button.htmx-request { animation: none; }
}

.conversation-list { list-style: none; padding: 0; margin: 0 0 14px; }
.conversation-list li {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid var(--line-soft);
  font-size: 14px;
}
.conversation-list li:last-child { border-bottom: none; }

/* ---------- coach memory ---------- */
.memory-list { list-style: none; padding: 0; margin: 16px 0 0; }
.memory { border-bottom: 1px solid var(--line-soft); padding: 13px 0; }
.memory:last-child { border-bottom: none; }
.memory-text { margin: 0 0 9px; font-size: 15.5px; color: var(--ink-2); }
.memory-meta { display: flex; align-items: center; gap: 12px; font-size: 12.5px; }
.memory-meta form { margin: 0; }
/*
  "Forget" and its kind: a quiet pill, not underlined red text.

  Underlined danger-coloured text read as a broken link and shouted louder than
  the note it belonged to. It is a small, reversible action on a line of metadata,
  so it should look like the badge next to it and only turn red on approach.
*/
.link-button {
  flex: 0 0 auto;
  border-radius: 999px;
  padding: 5px 12px;
  border: 1px solid var(--line);
  background: none;
  color: var(--mut);
  cursor: pointer;
  font-family: var(--cond);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  text-decoration: none;
  transition: border-color .15s ease, color .15s ease;
}
.link-button:hover, .link-button:focus-visible {
  border-color: rgba(255, 92, 92, .5);
  color: var(--danger);
}
/* Pushed to the far end of the metadata line, away from the badge and date. */
.memory-meta .link-button, .memory-meta form { margin-left: auto; }

/* ---------- footer ---------- */
.site-footer {
  position: relative;
  z-index: 1;
  margin-top: 40px;
  border-top: 1px solid var(--line-soft);
  background: linear-gradient(180deg, var(--field-soft), transparent);
}
.footer-inner {
  max-width: 520px;
  margin: 0 auto;
  padding: 26px 16px 34px;
  text-align: center;
}
.footer-brand { display: flex; align-items: baseline; justify-content: center; gap: 10px; }
.footer-logo {
  font-weight: 800;
  font-size: 17px;
  color: var(--accent-text);
}
/*
  The same gradient as the mark and the page heading. Declared here rather than beside them, and that
  is not tidiness: the rule above is a thousand lines further down the file, so at equal specificity
  it wins and the footer kept a flat accent while carrying a gradient nobody could see. Caught by
  reading the computed colour, which said rgb(255, 30, 142) with a linear-gradient sitting behind it.
*/
@supports (background-clip: text) or (-webkit-background-clip: text) {
  .footer-logo {
    background: linear-gradient(100deg, var(--accent) 35%, var(--accent-2));
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}
.footer-tagline {
  font-family: var(--cond);
  font-size: 12.5px;
  letter-spacing: 2.2px;
  text-transform: uppercase;
  color: var(--mut2);
}
.footer-links {
  display: flex;
  justify-content: center;
  gap: 18px;
  margin-top: 14px;
  font-family: var(--cond);
  font-size: 13px;
  letter-spacing: 1.8px;
  text-transform: uppercase;
}
/* "EN" is two characters, and it was a 16x20 target. The row keeps its look; each link gets the
   44px box a thumb needs, which is also what stops EN and IT being one mis-tap apart. */
.footer-links a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0 6px;
  color: var(--mut);
}
.footer-links a:hover { color: var(--accent-text); text-decoration: none; }
.footer-note { margin-top: 14px; font-size: 12.5px; color: var(--mut2); line-height: 1.6; }
.footer-copy { margin-top: 10px; font-size: 12px; color: var(--mut2); }

/* ---------- hamburger menu ---------- */
/* Smooth scrolling for the in-page anchors, with room for the sticky bar. */
html { scroll-behavior: smooth; }
/* Sotto la barra appiccicata, che adesso e' dieci pixel piu' alta. */
section[id] { scroll-margin-top: 90px; }

/*
  The bar is sticky, so whatever scrolls under it must not read through it.
  A translucent background plus backdrop-filter looked right until you scrolled a
  heading behind it - and backdrop-filter is exactly the property that is missing
  or disabled on the widest range of devices, so the fallback has to stand on its
  own. Nearly-opaque background, with the blur left in as decoration for browsers
  that have it.
*/
nav.top {
  position: sticky;
  top: 0;
  z-index: 30;
  background: var(--bar);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line-soft);
}

.rbtn {
  width: 44px;
  height: 44px;
  flex: 0 0 auto;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--mut);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.rbtn:hover { border-color: var(--accent-line); color: var(--accent-text); }

/* Lo spazio si apre prima del tema, non prima dell'hamburger: cosi' i due controlli stanno
   insieme a destra e il marchio ha tutta la sinistra. Con l'auto sull'hamburger la luna restava
   appiccicata al logo, in mezzo al niente. */
.top .appearance { margin-left: auto; }
/* Margine a zero, e non e' pignoleria.

   L'hamburger e' un <label> - e' il trucco che apre il menu senza JavaScript - quindi si prendeva
   la regola dei <label> dei moduli, "margin: 16px 0 8px". Un margine verticale asimmetrico dentro
   una riga centrata sposta l'elemento di meta' della differenza: quattro pixel piu' in basso della
   luna accanto, che e' lo scarto che si vedeva. */
.burger { margin: 0; flex-direction: column; gap: 4px; }
.burger span {
  display: block;
  width: 17px;
  height: 1.8px;
  border-radius: 2px;
  background: currentColor;
  transition: transform .18s ease, opacity .18s ease;
}
/* Checkbox-driven, so the menu needs no JavaScript at all. */
.menu-toggle:checked + .burger { border-color: var(--accent-line); color: var(--accent-text); }
.menu-toggle:checked + .burger span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.menu-toggle:checked + .burger span:nth-child(2) { opacity: 0; }
.menu-toggle:checked + .burger span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

/*
  Side drawer.

  Slides in from the right over a dimmed page. Driven entirely by the checkbox:
  no script, so it survives a blocked or failed app.js, and it needs no inline
  handler that the Content-Security-Policy would have to allow.

  Transform + visibility rather than `display`, so the slide can be animated and
  so the panel is genuinely non-interactive while closed - `visibility: hidden`
  takes it out of hit-testing and out of the tab order, which `opacity: 0` alone
  would not.
*/
.menu-toggle {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
/* Focusing the hidden checkbox must still show on the button the user sees. */
.menu-toggle:focus-visible + nav.top .burger,
.menu-toggle:focus-visible ~ nav.top .burger {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.menu-scrim {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: var(--scrim);
  opacity: 0;
  visibility: hidden;
  transition: opacity .22s ease, visibility .22s;
  cursor: pointer;
}

.menu-panel {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 41;
  width: min(300px, 84vw);
  padding: 18px 16px calc(24px + env(safe-area-inset-bottom, 0px));
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
  border-left: 1px solid var(--line);
  background: var(--panel);
  box-shadow: var(--shadow-panel);
  transform: translateX(100%);
  visibility: hidden;
  transition: transform .24s ease, visibility .24s;
}

.menu-toggle:checked ~ .menu-scrim { opacity: 1; visibility: visible; }
.menu-toggle:checked ~ .menu-panel { transform: translateX(0); visibility: visible; }

@media (prefers-reduced-motion: reduce) {
  .menu-scrim, .menu-panel { transition: none; }
}

.menu-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}
.menu-title {
  font-family: var(--cond);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 3px;
  text-transform: uppercase;
  color: var(--mut2);
}
.menu-close { width: 36px; height: 36px; font-size: 22px; line-height: 1; }

.menu-panel a, .menu-logout {
  display: block;
  width: 100%;
  padding: 12px 12px;
  border: none;
  border-radius: 10px;
  background: none;
  color: var(--mut);
  font-family: var(--cond);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  text-align: left;
}
/*
  Il passaggio del dito, il fuoco da tastiera - e la pagina in cui sei gia'.

  "Quando apro il menu la pagina corrente, se presente nel menu, deve essere evidenziata come se
  fossi in hover." Sono lo stesso aspetto perche' dicono la stessa cosa - "questa qui" - e darle due
  aspetti diversi sarebbe stato inventare un secondo linguaggio per un'informazione sola.

  Una differenza c'e', ed e' voluta: la voce corrente porta anche un filo dell'accento sul fianco
  sinistro. Il passaggio del dito e' una cosa che succede e finisce; dove sei e' una cosa che resta,
  e resta anche quando il dito e' passato oltre e ha acceso un'altra riga.
*/
.menu-panel a:hover, .menu-logout:hover,
.menu-panel a:focus-visible,
.menu-panel a[aria-current="page"] {
  background: var(--accent-soft);
  color: var(--accent-text);
  text-decoration: none;
}
.menu-panel a[aria-current="page"] {
  box-shadow: inset 3px 0 0 var(--accent);
}
/* ---------- the drawer's eleven entries ----------

   One line each, and each of them a link out of the menu. This was briefly a tree of <details>
   with numbers down the side; it read as a table of contents, the longer labels wrapped, and
   opening a section shifted everything below it while the athlete was reaching for something else.

   Nothing here wraps. The two longest labels - "Piano di allenamento AI", "I tuoi dati e
   abbonamento" - are what the sizing is set against: slightly tighter letter-spacing than the rest
   of the interface. nowrap rather than an ellipsis, because a menu entry that has been cut off is
   not a menu entry.

   There was a second style here, .menu-soon, for an entry listed before it was built: text with a
   badge instead of a link. Every section it was written for has shipped, so nothing rendered it any
   more - forty lines of stylesheet, a message key and a test guarding a state the application can no
   longer be in. Gone. If a section is ever listed early again it is a badge and a span, which is
   less work than reading dead rules for the rest of the project. */
.menu-panel a {
  white-space: nowrap;
  letter-spacing: 1.2px;
  font-size: 14px;
}

.menu-sep { display: block; height: 1px; margin: 10px 4px; background: var(--line-soft); }
.menu-langs { display: flex; gap: 4px; }
.menu-langs a {
  width: auto;
  min-height: 44px;
  min-width: 44px;
  padding: 8px 14px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.menu-logout {
  display: flex;
  align-items: center;
  min-height: 44px;
  margin-top: auto;
  color: var(--danger);
  cursor: pointer;
}
.menu-panel form { margin: 0; }

/* ---------- section headers and chips ---------- */
.hero { padding: 14px 0 2px; }
.card-top { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.status {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: var(--cond);
  font-size: 12.5px;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--accent-text);
}
.status .dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 8px var(--accent);
}
/* "All" is four characters and was a 38x20 target in the corner of a card. The type stays the
   size it is; the box around it reaches the thumb minimum. */
.link-more {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 4px;
  font-family: var(--cond);
  font-size: 13px;
  letter-spacing: 1.8px;
  text-transform: uppercase;
}

/* The .chip rules lived here: a pill with a halo, used by the coach card's three suggested
   questions and by nothing else. The card is gone, so they are. */

/* The native file input renders a grey system button that ignores the theme.
   ::file-selector-button is the standards-track way to restyle it - no wrapper
   markup, no JavaScript, and the input stays a real input for accessibility. */
input[type="file"] {
  border-radius: 999px;
  padding: 10px 10px 10px 16px;
  color: var(--mut);
  cursor: pointer;
}
input[type="file"]::file-selector-button {
  margin-right: 14px;
  padding: 10px 18px;
  border: 1px solid var(--accent-line);
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent-text);
  font-family: var(--cond);
  font-size: 13.5px;
  font-weight: 600;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  cursor: pointer;
}
input[type="file"]::file-selector-button:hover {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  box-shadow: 0 0 16px color-mix(in srgb, var(--accent) 20%, transparent);
}
input[type="file"]:hover { border-color: var(--accent-line); }

/* ---------- colour selector, top right ---------- */
/*
  Closed it is one small pill: the accent in force and the mode in force, which is all the athlete
  needs to see from the top bar. Open it holds the four colours and the switch.

  A <details>, so it opens with no JavaScript. The panel is absolutely positioned and the <details>
  is the containing block, which is what keeps the bar from growing taller when it opens - a top bar
  that changes height pushes the whole page down.

  Every rule here is nested under .appearance, and that is not tidiness: these are buttons inside
  nav.top, and "nav.top button" scores one class plus two elements, which beats a bare ".swatch" of
  one class. Measured the hard way - the swatches first rendered as the bar's dark pills with no
  colour at all, because the generic rule won.
*/
.appearance {
  position: relative;
  margin: 0 4px 0 0;
}

/*
  ---------- the language, beside the colours ----------

  The same pill as the colour control, and deliberately so: two controls that do the same kind of thing
  - change how the application presents itself - should look like two of one thing rather than two
  inventions. It takes the auto margin that used to push the colour pill right, so the pair sits
  together at the end of the bar.

  It reuses .appearance-panel for the dropdown. One definition of "a small panel hanging off a control
  in the top bar", so the two cannot drift apart by a pixel.
*/
/* A thin outline, because the white band of either flag would otherwise bleed into a light page. */
.flag { display: block; border: 1px solid var(--line); border-radius: 2px; }
.appearance > summary {
  display: flex;
  align-items: center;
  min-height: 44px;
  gap: 8px;
  /* Quattordici ai lati e non dieci: con dieci il pallino toccava il bordo della pillola, e su un
     controllo tondo due cerchi che si sfiorano si notano prima di qualunque altra cosa. */
  padding: 5px 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--field);
  color: var(--mut);
  cursor: pointer;
  list-style: none;
}
.appearance > summary::-webkit-details-marker { display: none; }
.appearance > summary:hover { border-color: var(--accent-line); color: var(--accent-text); }
.appearance[open] > summary { border-color: var(--accent-line); color: var(--accent-text); }

/*
  Il pannello del tema si appende sotto al suo pulsante, e non sta nel flusso della pagina.

  Questa regola era scritta per due controlli - il tema e la lingua - con i due selettori
  raggruppati. Togliendo la lingua ho cancellato il blocco intero insieme al suo secondo selettore,
  e il pannello e' rimasto senza position: absolute: apriva dentro la barra, spingendo il marchio e
  i colori in mezzo alla pagina.

  E' il rischio di cancellare CSS con un'espressione regolare: un selettore raggruppato non e' una
  riga, e una riga sola di quel gruppo si porta via le dichiarazioni di tutti. Adesso il selettore
  e' uno, quindi la prossima potatura non puo' fare lo stesso danno.
*/
.appearance .appearance-panel {
  position: absolute;
  right: 0;
  top: calc(100% + 8px);
  z-index: 40;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--panel);
  box-shadow: var(--shadow-panel);
}

/* No gap: each button already carries 13px of padding on every side, so the dots sit 26px apart
   with 44px of target between their centres and nothing overlaps. */
.appearance .appearance-row { display: flex; align-items: center; gap: 0; }

/* The dots carry their colour as a literal hex rather than var(--accent): each one has to show the
   colour it *would* switch to, and reading them from the current accent would paint four identical
   dots. They are the only place in the stylesheet that names a colour outside the palette. */
/*
  The dot stays 18px; the box around it is 44.

  A coloured dot the size of a shirt button is fine to look at and bad to hit with a thumb - and
  these sit four in a row, so a miss picks the wrong colour rather than nothing. Padding gives the
  button its 44px, and background-clip keeps the colour inside the 18px core, so nothing on screen
  changes. Scoped to the buttons in the panel: the same class marks the dot inside the closed pill,
  which is a span and must stay small.
*/
/* Sedici e non diciotto: il pallino accompagna la luna, non compete con lei. */
.appearance .swatch {
  width: 16px;
  height: 16px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--line);
  cursor: pointer;
}
/* Dentro il pannello restano grandi: li' sono il controllo da premere, non un'indicazione. */
.appearance-panel button.swatch {
  width: 18px;
  height: 18px;
  position: relative;
  box-sizing: content-box;
  padding: 13px;
  border: none;
  background-clip: content-box;
  background-origin: content-box;
}
/* The ring on the active one is drawn around the core rather than the padding, or it would circle
   the whole 44px target. */
.appearance-panel button.swatch.is-current { box-shadow: none; }
.appearance-panel button.swatch.is-current::after {
  content: "";
  position: absolute;
  inset: 10px;
  border-radius: 50%;
  box-shadow: 0 0 0 1.5px var(--ink-2);
}
.appearance .swatch:hover { transform: scale(1.12); }
/* The active one keeps a ring in the page's own ink, which reads on both surfaces - a ring in the
   accent itself would vanish against the dot it surrounds. */
.appearance .swatch.is-current {
  box-shadow: 0 0 0 2px var(--panel), 0 0 0 3.5px var(--ink-2);
}
/* In the closed pill the ring would collide with the border, and there is nothing to distinguish it
   from anyway: it is the only dot there. */
.appearance > summary .swatch.is-current { box-shadow: none; cursor: inherit; }
/*
  I quattro pallini, e il secondo posto da cui si guardano.

  .accent-choice e' il gruppo delle pagine pubbliche: stessa forma e stessi colori del pannello
  dell'aspetto, perche' e' la stessa scelta fatta prima di avere un account invece che dopo. Il
  selettore e' aggiunto qui e non riscritto sotto, cosi' i quattro valori restano scritti una volta
  sola - sono gia' gli unici colori nominati fuori dalla tavolozza.
*/
.appearance .swatch-pink,
.accent-choice .swatch-pink { background: #ff1e8e; }
.appearance .swatch-green,
.accent-choice .swatch-green { background: #2bff88; }
.appearance .swatch-orange,
.accent-choice .swatch-orange { background: #ff7a1a; }
.appearance .swatch-tiffany,
.accent-choice .swatch-tiffany { background: #2ee6df; }
/* I due pallini delle palette che portano anche il fondo: un mezzo cerchio per il fondo e uno per
   l'accento, perche' un pallino tutto giallo accanto a uno tutto verde non dice che con quello
   cambia anche la pagina. */
.appearance .swatch-volt,
.accent-choice .swatch-volt { background: linear-gradient(135deg, #1a1c1e 50%, #d7ff2e 50%); }
.appearance .swatch-ice,
.accent-choice .swatch-ice { background: linear-gradient(135deg, #0a1017 50%, #4fe3b0 50%); }

/* In the compact header icon, keep the diagonal fill out of the translucent border. Otherwise the
   coloured half leaks through as two small arcs where the diagonal meets the circle. */
.appearance > summary .swatch-volt,
.appearance > summary .swatch-ice { background-clip: padding-box; }

/*
  La fila di pallini delle pagine pubbliche: forma, misura e anello.

  Sta qui e non nel foglio della vetrina perche' la stessa fila e' anche nel form di registrazione,
  che carica solo questo foglio. Sono <label> attaccate a dei radio, non bottoni come nel pannello
  dell'aspetto: senza JavaScript un bottone dovrebbe inviare un form per farsi sentire e un radio
  no, ed e' l'unica differenza fra le due.
*/
.accent-choice { display: flex; align-items: center; }
/*
  Il pallino resta 18, la zona da premere e' 44 - lo stesso conto del pannello dell'aspetto e per
  la stessa ragione: quattro cerchi in fila grandi come un bottone di camicia si sbagliano, e
  sbagliarli qui non prende niente, prende il colore accanto. Il colore lo mette la classe
  .swatch-* qui sopra, e background-clip lo tiene dentro i 18 invece di riempire i 44.
*/
.accent-choice .swatch {
  position: relative;
  display: block;
  box-sizing: content-box;
  width: 18px;
  height: 18px;
  padding: 13px;
  border: none;
  border-radius: 50%;
  background-clip: content-box;
  background-origin: content-box;
  cursor: pointer;
  transition: transform .18s ease;
}
.accent-choice .swatch:hover { transform: scale(1.1); }
/*
  L'anello del colore scelto, disegnato attorno al pallino e non attorno ai 44px, altrimenti
  cerchierebbe tutta la zona da premere. Nell'inchiostro della pagina e non nell'accento: un anello
  del colore del pallino che lo circonda sparisce dentro il pallino stesso.

  Su ::after e non come box-shadow sull'elemento, cosi' non dipende dal colore che ha dietro - la
  stessa fila sta sulla barra della vetrina e su una carta nella registrazione.
*/
.accent-pick:checked + .swatch::after,
.accent-pick:focus-visible + .swatch::after {
  content: "";
  position: absolute;
  inset: 10px;
  border-radius: 50%;
  box-shadow: 0 0 0 1.5px var(--ink-2);
}
/* Il radio e' fuori schermo, quindi il fuoco da tastiera lo deve mostrare la label: senza questo
   chi naviga con il tab non vede su quale colore si trova. Un alone piu' largo dell'anello sopra,
   cosi' "ho il fuoco qui" e "questo e' quello scelto" restano due cose diverse. */
.accent-pick:focus-visible + .swatch {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ink-2) 40%, transparent);
}
@media (prefers-reduced-motion: reduce) {
  .accent-choice .swatch { transition: none; }
  .accent-choice .swatch:hover { transform: none; }
}

/* Named, not an icon: inside an open panel there is room for a word, and "Light" says what pressing
   it does in a way a sun cannot. */
.appearance .mode-toggle {
  min-height: 44px;
  padding: 7px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--field);
  color: var(--ink-2);
  font-family: var(--cond);
  font-size: 13px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  cursor: pointer;
}
.appearance .mode-toggle:hover { border-color: var(--accent-line); color: var(--accent-text); }

/*
  ---------- how much background ----------

  Three named steps under the mode switch: as drawn, half, none. Words rather than a slider, because
  there are three answers and a slider would invite hunting for a fourth.

  Its own row with a caption, so it does not read as a fourth mode toggle. Each step is 44px tall like
  everything else in this panel: it is a control on a phone, and the fact that it is a small word does
  not make it a small target. The one in force is filled as well as marked with aria-current - never
  colour alone.
*/
.appearance .appearance-arena {
  gap: 6px;
  flex-wrap: wrap;
  padding-top: 6px;
  border-top: 1px solid var(--line-soft);
  margin-top: 4px;
}
.appearance .appearance-caption {
  width: 100%;
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--mut2);
}
.appearance .arena-step {
  min-height: 44px;
  padding: 6px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--field);
  color: var(--ink-2);
  font-family: var(--cond);
  font-size: 12.5px;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  cursor: pointer;
}
.appearance .arena-step:hover { border-color: var(--accent-line); color: var(--accent-text); }
.appearance .arena-step.is-current {
  border-color: var(--accent-text);
  background: var(--accent-soft);
  color: var(--accent-ink);
}

/* One of each pair is shown, decided by the attribute on <html> - so flipping that attribute flips
   the icon and the label with it, which is what makes the switch instant instead of a page load. */
.appearance .icon-dark, .appearance .icon-light { display: none; }
:root[data-theme="dark"] .appearance .icon-dark { display: inline; }
:root[data-theme="light"] .appearance .icon-light { display: inline; }
/* Sulla vetrina l'attributo resta "dark" per sempre - il chiaro li' e' una casella di spunta - e
   senza queste due righe si vedrebbero luna e sole insieme. */
:root:has(.lp-theme-light:checked) .appearance .icon-light { display: inline; }
:root:has(.lp-theme-light:checked) .appearance .icon-dark { display: none; }

@media (max-width: 380px) {
  .appearance > summary { padding: 4px 8px; gap: 5px; }
  .appearance .swatch { width: 16px; height: 16px; }
}

/* ---------- bottom bar with central microphone ---------- */
/*
 * From the reference design: a fixed bar with home and profile at the edges and
 * dictation as the large glowing button in the middle. Blurred rather than
 * opaque so content scrolling underneath stays visible.
 */
/* Floating: lifted off the bottom edge and rounded, rather than a strip welded to it.
   Two reasons beyond looks. A bar flush with the edge collides with the phone's own home
   gesture area, and one that spans the full width reads as part of the browser chrome
   instead of part of the app. */
/* The bar sits on the bottom of what the athlete can actually see, which on a phone is not the
   bottom of the page's own viewport.

   Reported from production: "quando scrollo in basso la barra rimane bloccata a metà schermo e
   quando scorro in alto torna a posto". Both halves of that are one cause. Scrolling down hides
   the browser's address bar, which grows the visible area without changing the viewport the page
   was laid out against, so a bar pinned to the layout viewport's bottom edge is left behind - and
   scrolling up puts the address bar back and the two agree again. --viewport-bottom is the gap
   between the two, measured by app.js from window.visualViewport and zero everywhere the question
   does not arise (a desktop browser, or scripting off).

   Centred with auto margins rather than left:50% and a translate. A transform on a fixed element
   is correct CSS, but it also hands the element to the compositor, and a composited fixed layer is
   exactly what mobile browsers reposition a frame late during a scroll. Two auto margins centre it
   with no layer at all. */
.bottom-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: calc(12px + var(--viewport-bottom, 0px) + env(safe-area-inset-bottom, 0px));
  z-index: 30;
  width: calc(100% - 24px);
  max-width: 480px;
  margin-inline: auto;
  border-radius: 999px;
  box-shadow: 0 8px 28px rgb(0 0 0 / .45);
  /* Two icons, the microphone, two icons. Symmetry is the requirement, not the aesthetic:
     the microphone is the one control that has to be hittable without aiming, so it sits
     dead centre, and an odd number of icons around it puts it off centre.

     Flex, not grid, and that is the fix for a real bug. With five named grid columns, an
     item that is display:none vacates its column instead of collapsing it: on every page
     where the microphone was hidden, the four icons packed into columns 1-4 and left an
     empty column on the right. Flex distributes whatever children are actually there, so
     the bar cannot end up lopsided again if one of them ever goes away. */
  display: flex;
  justify-content: space-around;
  align-items: center;
  padding: 10px 22px;
  border: 1px solid var(--line-soft);
  background: var(--bar);
  backdrop-filter: blur(16px);
}
.bar-icon {
  width: 46px;
  height: 46px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  color: var(--mut);
}
.bar-icon:hover { color: var(--accent-text); text-decoration: none; }

.mic-fab {
  position: relative;
  width: 62px;
  height: 62px;
  /* Lifted above the bar, the way the reference shows it. */
  margin-top: -26px;
  border-radius: 50%;
  border: none;
  background: var(--accent);
  color: var(--on-accent);
  display: flex;
  align-items: center;
  justify-content: center;
  /* The fluorescent look comes from stacked glows, not from the fill. */
  box-shadow:
    0 0 0 6px var(--bar),
    0 0 26px color-mix(in srgb, var(--accent) 80%, transparent),
    0 0 70px color-mix(in srgb, var(--accent) 40%, transparent);
}
.mic-fab:active { transform: scale(.94); }
/* It is an <a> in the markup - a link to the coach that script upgrades into a dictation
   control - so it needs the link decorations turned off. */
.mic-fab, .mic-fab:hover { color: var(--on-accent); text-decoration: none; }

/* Recording state. Movement, not just colour: a ring that keeps expanding is
   what actually reads as "it is listening right now". */
.mic-fab.listening {
  background: #fff;
  color: var(--accent-text);
  box-shadow: 0 0 0 6px var(--bar), 0 0 38px color-mix(in srgb, var(--accent) 100%, transparent), 0 0 100px color-mix(in srgb, var(--accent) 55%, transparent);
}
/* Un'icona per volta: microfono da spento, la freccia dell'invio mentre ascolta. Il cambio e' qui
   e non in app.js, che si limita ad accendere e spegnere la classe - due disegni che compaiono e
   scompaiono sono un lavoro da foglio di stile. */
.mic-fab-send { display: none; }
.mic-fab.listening .mic-fab-mic { display: none; }
.mic-fab.listening .mic-fab-send { display: block; }
.mic-pulse { position: absolute; inset: 0; border-radius: 50%; pointer-events: none; }
.mic-fab.listening .mic-pulse {
  border: 2px solid var(--accent);
  animation: mic-ring 1.4s ease-out infinite;
}
@keyframes mic-ring {
  0% { transform: scale(1); opacity: .9; }
  100% { transform: scale(2); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .mic-fab.listening .mic-pulse { animation: none; border-color: var(--accent-text); }
  html { scroll-behavior: auto; }
}

.mic-status {
  position: fixed;
  left: 50%;
  bottom: 92px;
  transform: translateX(-50%);
  z-index: 31;
  padding: 6px 14px;
  border-radius: 999px;
  background: var(--bar);
  border: 1px solid var(--accent-line);
  font-family: var(--cond);
  font-size: 12.5px;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--accent-text);
}

/*
  Room so the fixed bottom bar never covers anything - e una volta sola.

  Lo spazio per la barra serve a chi sta in fondo alla pagina, e in fondo alla pagina c'e' sempre il
  pie': e' nel guscio comune, senza condizioni, quindi ogni pagina ce l'ha. Il guscio del contenuto
  invece non tocca mai la barra, perche' fra i due c'e' il pie' - lo spazio che si teneva era una
  seconda riserva per un pericolo che aveva gia' qualcun altro, e a schermo erano centottanta pixel
  di niente fra l'ultima scheda e il pie'.

  Riportato guardando la home: "quando arrivi in fondo alla home... far finire correttamente la
  home". Adesso il guscio tiene solo il respiro che gli serve per staccarsi dal pie'.

  --bar-h e' l'altezza vera della barra (10 + 44 + 14 piu' il bordo), e l'inserto di sicurezza copre
  la barretta di casa su iOS.
*/
.app-shell { padding-bottom: 56px; }
.site-footer { padding-bottom: calc(var(--bar-h) + 28px + env(safe-area-inset-bottom, 0px)); }

/*
  ---------- rimosso: html { overscroll-behavior-y: none } ----------

  C'era per il rimbalzo di fine pagina: "quando arrivi in fondo alla home fa scorrere ancora e
  sposta la tab rapida". Trascinando oltre l'ultimo pixel il browser sposta tutta la pagina, e con
  lei quello che sta fisso sopra, che si stacca dal fondo dello schermo e sale.

  Toglierla era il prezzo, e il commento di allora lo diceva: "si perde il trascinamento per
  ricaricare, ed e' un prezzo che si paga volentieri". Non lo era: "perche' non funziona piu' il
  refresh scrollando in basso la scheda, e rimettilo".

  E non c'e' una via di mezzo. Il rimbalzo in fondo e il trascinamento per ricaricare in cima sono
  lo stesso comportamento del browser visto ai due capi della pagina, e overscroll-behavior li
  governa insieme: "none" li spegne tutti e due, "contain" toglie comunque il trascinamento per
  ricaricare, e solo il valore predefinito li lascia tutti e due. Quindi resta il predefinito, che e'
  nessuna regola.

  Se la barra in basso dovesse tornare a sollevarsi in fondo alla home, la strada e' compensare il
  rimbalzo dentro a --viewport-bottom - dove oggi lo scarto negativo viene azzerato di proposito,
  vedi gap() in app.js - e non spegnere di nuovo un gesto del telefono.
*/

/* ---------- logo mark ---------- */
.logo { display: inline-flex; align-items: center; min-height: 44px; gap: 10px; }
/*
  ---------- the mark ----------

  Replicated from the official logo: a ring whose stroke runs from the accent at its foot to the
  second brand colour at its head, beside "Koræi" with the same run across the last two letters.

  The ring is an inline SVG rather than a bordered box, because a border cannot hold a gradient
  without a masking trick that behaves differently in three browsers. The gradient stops read the CSS
  custom properties, so the mark follows the athlete's palette with nothing duplicated per colour.

  Mixed case, not KORÆI. The official mark is "Koræi", and the application was shouting it.
*/
.logo-ring { width: 24px; height: 24px; display: block; overflow: visible; }
/* The gradient's two ends. Declared here rather than as stop-color="var(--accent)" on the element,
   because var() in an SVG presentation attribute is not honoured everywhere and the fallback is
   black - which on this bar is an invisible mark. */
/*
  THE MARK FOLLOWS THE ACCENT, on the athlete's own screen.

  Asked for directly: "quando cambio colore voglio che anche il logo si adegui al nuovo colore". It
  is the opposite of what was here, and the trade is worth naming: a logo that changes is a weaker
  logo, because recognition is built on it being the same everywhere. What is bought back is that the
  header stops being the one thing on the page that ignores a choice the athlete just made - which on
  the orange accent read as a leftover rather than as a brand.

  The run keeps its shape - a colour held, a middle, an end - so it is the same mark drawn in the
  chosen palette rather than a flat block of one colour. The middle is mixed rather than declared,
  which is what keeps a fifth accent from needing a fifth line here.

  Outside the page the mark does not change and must not: /img/koraei-logo.svg and the favicon are
  fetched on their own, with no page and no custom properties around them, and they stay the brand's
  pink through violet to blue. So the printed mark, the browser tab and anything we send out are one
  logo; the header is that logo in the athlete's colour.
*/
:root {
  --mark-1: var(--accent);
  --mark-2: color-mix(in srgb, var(--accent) 50%, var(--accent-2));
  --mark-3: var(--accent-2);
}
.logo-ring .mark-stop-foot { stop-color: var(--mark-1); }
.logo-ring .mark-stop-head { stop-color: var(--mark-3); }
.logo-ring circle {
  /* The glow the mark has in the original, as a filter on the stroke rather than a second ring. */
  filter: drop-shadow(0 0 5px color-mix(in srgb, var(--mark-1) 65%, transparent));
}
/*
  THE WHOLE WORD CARRIES THE RUN, not the last two letters.

  It used to be "Kor" in the ink colour and "æi" in a gradient, which is a different mark from the one
  the brand actually uses: there the pink starts at the K, holds through "Kor", turns violet across
  the ligature and arrives blue at the i. One run over five letters.

  background-clip: text needs the text's own colour to be transparent, and that is a one-way door:
  where the property is unsupported the letters vanish entirely. So the colour is set first and
  cleared only inside @supports, which leaves a solid brand pink as the fallback rather than nothing.

  The stops match /img/koraei-logo.svg exactly, including the pink hold at 30%: over five letters an
  even run turns the "o" violet a whole letter early.
*/
.logo-word {
  color: var(--mark-1);
  font-weight: 800;
  font-size: 21px;
  letter-spacing: -0.4px;
  text-shadow: none;
}
@supports (background-clip: text) or (-webkit-background-clip: text) {
  .logo-word {
    background: linear-gradient(100deg, var(--mark-1) 0%, var(--mark-1) 30%,
                                var(--mark-2) 66%, var(--mark-3) 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}
/*
  The ligature keeps a class of its own and no longer carries a colour of its own.

  It must not: a background on the child would be clipped to the CHILD's text and would restart the
  run at the "æ", which is the five-little-rainbows fault in miniature. The parent's background is
  clipped to all of its text, the child's glyphs included, so the correct thing here is to declare
  nothing and let it show through.

  The class stays in the markup because the mark is written "Kor" + "æi" wherever it is set, and a
  hook on the brand's one typographic decision is worth keeping.
*/
.logo-accent { color: inherit; }

/*
  ---------- the training icons ----------

  The two icons that mean "a workout" - the heartbeat and the barbell - take the second brand colour
  rather than the accent, which is the light blue of the logo on the default palette. Asked for in
  those words, and it earns its keep beyond taste: on a page where the accent marks everything the
  athlete can act on, a training icon in a different colour reads as a category rather than as a
  control. It follows the palette, so it is never a colour the athlete did not choose.

  Two declarations, because an icon's colour is set from several places. Written as colour alone this
  looked correct and painted nothing: `.card-title-row > .ico` and `.metric-label > .ico` are more
  specific, so every icon inside a card heading or a tile label stayed the accent - which is most of
  them. Measured in a browser, not read off the source.

  So it also sets a tint, which the rules that colour an icon read as `var(--ico-tint, var(--accent))`.
  A custom property set on the element itself wins wherever it is read, with no specificity race and
  no !important, and a second tinted family later is one line rather than four.
*/
.ico-training {
  --ico-tint: var(--accent-2);
  color: var(--ico-tint);
}

/* ---------- text must never overflow its card ---------- */
/* clip, not hidden. The two look identical - neither shows the overflow, neither offers a
   sideways scrollbar - but `hidden` makes the element a scroll container, and a scroll container
   on <body> is what several mobile browsers treat as a reason to stop honouring position: fixed
   on the page. `clip` cuts the overflow off without creating one. */
body { overflow-x: clip; }
.card, .bubble, .job, .memory { min-width: 0; }
.bubble-text, .memory-text, .job-name, .muted, p {
  overflow-wrap: anywhere;
  word-break: break-word;
}
.bubble-text pre { overflow-x: auto; }
.job-main { align-items: flex-start; }
.job-title { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.job-time { font-family: var(--cond); font-size: 12px; letter-spacing: 1.4px; color: var(--mut2); }
.upload-results li span:first-child { min-width: 0; overflow-wrap: anywhere; }


/* ---------- readiness ring ---------- */
/*
  The ring sits at the top of the card, centred, with the score inside it.
  Side by side with the numbers it competed with them; alone at the top it reads
  as the headline it is meant to be.
*/
.readiness {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 10px;
  margin: 4px 0 22px;
}
.gauge { position: relative; width: 156px; height: 156px; flex: 0 0 auto; }

/*
  A soft halo behind the whole ring, not just the arc.

  The arc has always carried its own drop-shadow, but on a low score the arc is a stub and the rest
  of the circle is unlit grey - so the one graphic the page is built around read as flat, which is
  what "put the glow back on the rest circle" was describing. This lights the circle itself: one
  wide, weak layer at 14%, behind everything, hit-testing nothing. Weak on purpose. The reading in
  the middle sits on top of it and has to stay crisp.
*/
.gauge::before {
  content: "";
  position: absolute;
  inset: 4%;
  border-radius: 50%;
  box-shadow: 0 0 26px color-mix(in srgb, var(--accent) 14%, transparent);
  pointer-events: none;
}

/*
  The hero ring. Bigger than a card gauge because it is the first thing on the
  page and carries the brand inside it, but capped against the viewport so it
  never crowds out the greeting on a small phone.
*/
/*
  156px, down from 228.

  The ring is the page's one graphic and it was taking 27% of the first screen on a phone - for a
  number that reads 100/100 for everybody who has just signed up, while the load tiles that explain
  it sat below the fold. At 156 both rows of tiles are on the first screen with it, which is the
  whole argument: the score and the figures it is made of belong on screen together.

  Still the largest thing on the page by some way, and the stroke goes up as the circle comes down
  so the arc keeps its weight rather than thinning into a hairline.
*/
.gauge-hero { width: min(156px, 44vw); height: min(156px, 44vw); }
.gauge-hero .gauge-track, .gauge-hero .gauge-arc, .gauge-hero .gauge-pulse { stroke-width: 9; }
.gauge-hero .gauge-score { font-size: clamp(30px, 9.5vw, 40px); }
.gauge-hero .gauge-outof { font-size: clamp(12px, 3.4vw, 15px); }
/*
  overflow: visible is load-bearing, not tidying.

  An <svg> clips to its own box by default, and the arc's pink drop-shadow needs
  about 30px of room while the viewBox leaves it 6. The glow was being sliced off
  square, so a circular gauge sat inside a visible pink rectangle. Letting it
  overflow is correct: a glow is decoration, it has nothing to collide with, and
  the alternative - padding the viewBox - shrinks the ring to make space for
  something that is not part of it.
*/
.gauge svg { width: 100%; height: 100%; overflow: visible; transform: rotate(-90deg); }
.gauge-track {
  fill: none;
  stroke: var(--track);
  stroke-width: 10;
}
.gauge-arc {
  fill: none;
  stroke: var(--accent);
  stroke-width: 10;
  stroke-linecap: round;
  filter: drop-shadow(0 0 12px color-mix(in srgb, var(--accent) 70%, transparent))
          drop-shadow(0 0 30px color-mix(in srgb, var(--accent) 28%, transparent));
  transition: stroke-dashoffset .6s ease;
}
/*
  ---------- l'anello si disegna, e sulla testa scocca una scintilla ----------

  "Fai una specie di esplosione di stelline quando arriva alla fine, ovviamente coerente con lo
  stile, nulla di troppo bambinesco."

  Due cose, e la prima serve alla seconda: l'anello arrivava gia' disegnato - il valore e' scritto
  nell'attributo dal server - quindi non esisteva nessun momento in cui "arriva alla fine". Adesso si
  disegna in sei decimi, insieme al numero che sale al centro, e quando la testa dell'arco si ferma
  la scintilla scocca li'.

  Sei raggi sottili e tre punte a quattro braccia, tutti del colore dell'accento. Niente coriandoli,
  niente stelle a cinque punte, niente di dorato: e' lo stesso rosa dell'anello che si spegne, che e'
  il modo in cui questa applicazione ha sempre segnato una cosa compiuta.

  Mezzo secondo e non si ripete. Un anello che scintilla a ogni sguardo diventa un'insegna, e questa
  e' la prima cosa che si legge aprendo la giornata.

  La "from" senza "to" nel disegno dell'arco non e' una dimenticanza: senza il secondo fotogramma il
  valore d'arrivo e' quello calcolato sull'elemento, cioe' il punteggio vero scritto dal server. E'
  l'unico modo di animare fino a un numero che il foglio di stile non conosce.
*/
/* Dentro alla regola del movimento, come tutto il resto. Stava fuori, ed era un difetto vero: chi
   ha chiesto meno movimento si vedeva comunque l'anello disegnarsi. Trovato contando le animazioni
   in pagina con la preferenza attiva - ne restava una. */
@media (prefers-reduced-motion: no-preference) {
  /*
    Due secondi e mezzo, chiesti guardandolo: "animazione anello iniziale 2,5 sec". Era un secondo,
    ed e' il primo disegno che si vede aprendo l'applicazione: un arco che si chiude in un secondo
    e' un'informazione che compare, in due e mezzo e' una misura che si forma.

    Tutto cio' che viene dopo slitta con lui e non e' un dettaglio: la scintilla deve scoccare
    quando l'arco SI FERMA - se restasse a 0,95s scoccherebbe a meta' disegno, e il battito che la
    segue partirebbe sopra un anello ancora in movimento.
  */
  .gauge-hero.is-visible .gauge-arc { animation: gauge-draw 2.5s cubic-bezier(.2, .75, .3, 1); }
}
@keyframes gauge-draw { from { stroke-dashoffset: 389.6; } }

/*
  ---------- e nell'istante in cui si ferma, l'anello da' un colpo ----------

  "Quando finisce di caricare cerchio, contestualmente all'apparizione delle scintille, cerchio deve
  fare come una sorta di battito: aumentare il raggio e poi riportarlo a valore predefinito."

  Un colpo solo, non il polso che viene dopo. Il polso e' .gauge-pulse, comincia a 3,4 e non finisce
  mai; questo dura sei decimi, parte a 2,45 - lo stesso istante dei raggi - e serve a una cosa
  precisa: e' l'anello che spinge fuori le scintille, non l'anello che respira. Sale in fretta e
  torna piano, come una cosa colpita.

  Sei per cento e non di piu'. La scala allarga anche il tratto, quindi il bordo esterno passa da
  136,5 a 140,8 unita' del disegno: oltre, l'anello arriverebbe sotto la base dei raggi, che e' la
  cosa che questa animazione doveva evitare.

  transform-box: view-box perche' l'origine sia il centro del disegno - (70, 70) - e non l'angolo
  del riquadro che contiene i tre cerchi.
*/
.gauge-ring { transform-box: view-box; transform-origin: 70px 70px; }
@media (prefers-reduced-motion: no-preference) {
  .gauge-hero.is-visible .gauge-ring { animation: gauge-beat .6s cubic-bezier(.2, .8, .3, 1) 2.45s; }
}
@keyframes gauge-beat {
  0% { transform: scale(1); }
  30% { transform: scale(1.06); }
  100% { transform: scale(1); }
}

/*
  ---------- e poi l'anello batte ----------

  "Una volta renderizzato il cerchio deve pulsare, come fosse sangue che scorre."

  Non un respiro ma un battito, che e' una cosa diversa e si riconosce: due colpi vicini e una pausa
  lunga - il secondo piu' debole del primo, come il secondo tono del cuore. Un'onda regolare avanti e
  indietro legge come "sta caricando"; questa legge come un polso, che e' esattamente cio' che
  l'anello misura.

  A 52 battiti al minuto, cioe' un ciclo ogni 1,15 secondi. Non e' un numero preso a caso: e' il
  polso a riposo di chi corre, e su una scheda che si chiama condizione fisica un ritmo da sessanta
  sarebbe stato quello di chi e' seduto.

  Comincia dopo la scintilla, quando l'anello ha finito di disegnarsi e di festeggiare: prima
  sarebbero tre cose che si muovono insieme sulla stessa figura.

  A pulsare e' un secondo arco sovrapposto, e a pulsare di lui e' solo l'opacita' - l'unica cosa che
  un browser sa animare senza ridisegnare nulla. L'alternativa era far respirare il filtro dell'arco
  vero, cioe' ricalcolare due sfocature sessanta volte al secondo per sempre, sulla pagina che resta
  aperta piu' a lungo di tutte.
*/
.gauge-pulse {
  fill: none;
  stroke: var(--accent);
  stroke-width: 10;
  stroke-linecap: round;
  opacity: 0;
  filter: drop-shadow(0 0 10px color-mix(in srgb, var(--accent) 90%, transparent))
          drop-shadow(0 0 26px color-mix(in srgb, var(--accent) 55%, transparent));
}
@media (prefers-reduced-motion: no-preference) {
  .gauge-hero.is-visible .gauge-pulse { animation: gauge-heartbeat 1.15s ease-out 3.4s infinite; }
}
@keyframes gauge-heartbeat {
  0% { opacity: 0; }
  5% { opacity: .6; }   /* il primo tono */
  13% { opacity: .07; }
  19% { opacity: .34; } /* il secondo, piu' debole */
  30% { opacity: 0; }
  100% { opacity: 0; }  /* e la pausa, che e' i due terzi del battito */
}

/* Lo stesso alone dell'anello, cosi' la scintilla e' fatta della stessa luce e non incollata
   sopra. Sul gruppo e non sui singoli pezzi: nove filtri in un disegno da 140 unita' sono nove
   passaggi di sfocatura per qualcosa che dura mezzo secondo. */
.gauge-spark {
  filter: drop-shadow(0 0 6px color-mix(in srgb, var(--accent) 60%, transparent));
  /* view-box, cosi' l'origine dello scoppio e' il centro dell'anello - (70, 70) - e non l'angolo
     del riquadro che contiene raggi e punte. */
  transform-box: view-box;
  transform-origin: 70px 70px;
}
.gauge-ray, .gauge-star { fill: var(--accent); opacity: 0; }
/* fill-box, o "left" e' il bordo sinistro dell'intero disegno e il raggio partirebbe da fuori. */
.gauge-ray { transform-box: fill-box; transform-origin: left center; }
.gauge-star { transform-box: fill-box; transform-origin: center; }

/*
  Lo scoppio, sul gruppo intero.

  "Rendile un po' piu' stile fuochi d'artificio." Un fuoco d'artificio non e' fatto di pezzi che
  compaiono e si spengono sul posto: e' una cosa che si allontana dal centro mentre si spegne. Quindi
  la partenza la danno i singoli raggi, come prima, ma il gruppo che li contiene si allarga per tutta
  la durata - e siccome l'origine e' il centro dell'anello, allargarlo vuol dire spingere fuori tutto
  quanto insieme.

  Ed e' anche la seconda meta' della richiesta: mentre l'anello si gonfia le scintille se ne vanno,
  quindi non c'e' nessun istante in cui il cerchio arriva addosso a loro.
*/
@media (prefers-reduced-motion: no-preference) {
  .gauge-hero.is-visible .gauge-spark { animation: gauge-burst 1.1s cubic-bezier(.1, .7, .3, 1) 2.45s; }

  /* Tutto parte quando l'arco si ferma: 2,45 contro i 2,5 esatti del disegno.

     La durata del disegno e' stata regolata tre volte - mezzo secondo, poi uno, ora due e mezzo -
     e ogni volta questi ritardi sono slittati con lei. Non sono numeri indipendenti: sono "un
     soffio prima della fine dell'arco", scritti in secondi perche' il CSS non sa sommare. Chi
     cambia i 2,5s sopra deve rifare anche questi, altrimenti la scintilla scocca a meta' disegno.
     Il battito comincia dopo, a 3,4, quando l'anello ha finito di disegnarsi e di festeggiare. */
  .gauge-hero.is-visible .gauge-ray { animation: gauge-ray .9s cubic-bezier(.12, .7, .25, 1) 2.45s; }
  /* Tre tempi invece di due. Dodici trattini che partono insieme sono un lampo; a coppie erano
     qualcosa che si apre; a terzine, con otto centesimi fra l'uno e l'altro, e' una salva - che e'
     il modo in cui uno scoppio si sente davvero, a ondate e non tutto in una volta. */
  .gauge-hero.is-visible .gauge-rays > g:nth-child(3n + 2) .gauge-ray { animation-delay: 2.53s; }
  .gauge-hero.is-visible .gauge-rays > g:nth-child(3n) .gauge-ray { animation-delay: 2.61s; }
  .gauge-hero.is-visible .gauge-star { animation: gauge-star 1s cubic-bezier(.2, .8, .3, 1) 2.55s; }
  .gauge-hero.is-visible .gauge-star--2 { animation-delay: 2.68s; }
  .gauge-hero.is-visible .gauge-star--3 { animation-delay: 2.81s; }
}
/* Tutto si allontana dal centro, e i sedici per cento sono presi sulla distanza dal centro, non
   sulla lunghezza del raggio: a x = 142 vuol dire dodici unita' piu' in fuori. */
@keyframes gauge-burst {
  0% { transform: scale(1); }
  100% { transform: scale(1.16); }
}
/*
  Il raggio parte corto e acceso, si allunga andandosene e muore sottile: e' una scia, non un
  trattino che compare. Niente translateX negativo in partenza - era il pezzo che finiva sotto
  l'anello quando questo si gonfia.
*/
@keyframes gauge-ray {
  0% { transform: translateX(0) scaleX(.25); opacity: 0; }
  12% { opacity: 1; }
  55% { transform: translateX(8px) scaleX(1.15); opacity: .9; }
  100% { transform: translateX(20px) scaleX(.45); opacity: 0; }
}
/* La punta ammicca due volte prima di spegnersi: e' quello che distingue una scintilla che si
   spegne da una stellina che sfuma. */
@keyframes gauge-star {
  0% { transform: scale(0) rotate(-40deg); opacity: 0; }
  28% { transform: scale(1.15) rotate(0deg); opacity: 1; }
  48% { transform: scale(.7) rotate(12deg); opacity: .45; }
  68% { transform: scale(1) rotate(22deg); opacity: .9; }
  100% { transform: scale(.3) rotate(40deg); opacity: 0; }
}

/*
  Two jobs, two elements. The outer box centres inside the ring; the inner line
  sits "64" and "/100" on a shared baseline.

  They cannot be the same element: `align-items: baseline` on a flex container
  aligns items *within* their line, it does not centre the line in the cross
  axis - so the reading rendered against the top of the ring instead of in the
  middle of it.
*/
.gauge-inner {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
}
.gauge-reading { display: flex; align-items: baseline; gap: 3px; }
.gauge-score {
  font-size: 46px;
  font-weight: 800;
  line-height: 1;
  color: var(--accent-text);
}
.gauge-outof { font-size: 17px; font-weight: 600; color: var(--mut); }
/* What the ring measures, under the ring. Quieter than the state below it: it names the
   measurement once and should not compete with the reading, which is the part that changes. */
.gauge-caption {
  margin: 10px 0 2px;
  font-family: var(--cond);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 2.4px;
  text-transform: uppercase;
  color: var(--mut2);
  text-align: center;
}

.gauge-state {
  margin: 0;
  font-family: var(--cond);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 3px;
  text-transform: uppercase;
  color: var(--ink);
}

/* ---------- section explainers ---------- */
/*
  Tap a section heading and it explains itself.

  Built on <details>/<summary>: the browser handles open/close state, keyboard
  access and screen-reader semantics, it works with scripts blocked, and it needs
  no inline handler - which matters, because our CSP allows none.

  The explanation flows *below* the heading rather than floating over the page.
  A floating panel was the first attempt and it could not be made to fit: anchored
  to a "?" sitting mid-card, on a 390px screen it ran 60px past the edge, and no
  amount of max-width fixes that without knowing where the "?" landed. Flowing
  inline, it is always exactly as wide as the card and can never be clipped. The
  cost is that opening one nudges the page down, which is the normal, expected
  behaviour of an accordion.
*/
.explain { flex: 1 1 auto; min-width: 0; }

.explain > summary {
  display: flex;
  align-items: center;
  gap: 10px;
  list-style: none;
  cursor: pointer;
}
.explain > summary::-webkit-details-marker { display: none; }
.explain > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.explain-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--field);
  color: var(--mut2);
  font-family: var(--cond);
  font-size: 12px;
  font-weight: 600;
  line-height: 1;
  user-select: none;
}
.explain > summary:hover .explain-mark,
.explain[open] .explain-mark { border-color: var(--accent-line); color: var(--accent-text); }

/*
  ---------- whether Strava is pushing, on the connections card ----------

  A small dot and two words, with the same "?" disclosure as the readiness ring. Not a hover
  tooltip: this is a phone, there is no hover, and a title attribute is an explanation nobody can
  reach with a thumb.

  The dot is decoration and says nothing on its own - the words beside it carry the state, which is
  why it is aria-hidden. It is not green/red either: neither state is an error. Push off means
  workouts arrive when the athlete presses the button, which is slower and perfectly fine, so "off"
  takes the muted colour the card uses for quiet facts and "on" borrows the accent.
*/
.strava-push { margin: 10px 0 2px; }
.strava-push > summary { gap: 8px; min-height: 32px; }
.push-state { font-size: 13.5px; color: var(--mut); }
.strava-push.is-live .push-state { color: var(--ink-2); }
.push-dot {
  width: 8px;
  height: 8px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--mut2);
}
.strava-push.is-live .push-dot {
  background: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}

.explain-body {
  margin: 12px 0 2px;
  padding: 12px 14px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line-soft);
  background: var(--field-soft);
  color: var(--mut);
  font-size: 13.5px;
  line-height: 1.65;
  text-align: left;
}

/* The readiness state: a centred summary, full-width body like every other
   explainer. Never a flex item sized to its own "?" - that is what let the text
   spill off both edges of the screen. */
.readiness-state { width: 100%; }
.readiness-state > summary { justify-content: center; }
.readiness-state .explain-body { text-align: center; }

/* Heading row: the explainer takes the space, any chip or link is pushed to the
   far end. */
.card-top { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.card-top > .status, .card-top > .microlbl, .card-top > .link-more { margin-left: auto; }

/* ---------- load figures under the hero ring ---------- */
/*
  The tiles now sit in the hero rather than in their own card further down. They
  keep their tile styling - each one is still a bordered box - but the wrapper
  adds no second border around them: a card inside the hero would put a frame
  around the frame.
*/
.load-block { margin-top: 22px; }

/* Centred to line up with the ring above it. */
.load-heading { width: 100%; }
.load-heading > summary { justify-content: center; }
.load-heading .microlbl { display: inline; }
.load-block .tiles { margin-top: 12px; }
.load-block .alert { text-align: left; }

/*
  ---------- what is left of the day, in two numbers ----------

  In the drawer, above the eleven destinations. It was on the home first - a block, then a row under
  the state - and both times the objection was the same: it pushed the load figures, the ones the
  ring is made of, down the page. Here it pushes nothing at all, and there is room to set the two
  numbers large enough to read at a glance.

  The figures live inside the <summary>, which is the part that is always visible: opening the menu
  has to show them, and the "?" is there to explain them. Put them in the body and they would only
  appear after a press, and a counter that hides is a counter that does not exist.
*/
.ai-left {
  margin: 0 4px 14px;
  padding: 13px 15px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line-soft);
  background: var(--field-soft);
}
.ai-left > summary { flex-wrap: wrap; gap: 8px; }
.ai-left-head {
  font-family: var(--cond);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 2.2px;
  text-transform: uppercase;
  color: var(--mut);
}
/* Su tutta la riga successiva: e' un <span> e non un <div> perche' sta dentro a <summary>, che
   accetta solo contenuto di testo. */
/*
  ---------- due misuratori, uno per riserva ----------

  "Deve essere una cosa del genere, graficamente come e' su Claude", con la schermata d'uso a
  fianco: ogni riserva e' una riga larga quanto il pannello - il nome a sinistra, "N% utilizzato" a
  destra - e sotto una barra piena per tutta la larghezza. La prima versione erano due colonnine
  strette con una barretta ciascuna: aveva preso l'idea di quella schermata e non la forma, e due
  barre larghe cinquanta pixel non si confrontano con niente.

  La barra si riempie con quello che e' stato SPESO. Una che si svuota legge come una batteria, che
  e' una cosa che si possiede; una che si riempie legge come un contatore, che e' una cosa che si
  consuma. Qui e' la seconda.

  Su tutta la riga successiva al titolo, e in colonna: sono <span> e non <div> perche' stanno dentro
  a <summary>, che accetta solo contenuto di testo.
*/
.ai-left-meters { flex: 0 0 100%; display: flex; flex-direction: column; gap: 14px; margin-top: 10px; }
.ai-left-meter { display: flex; flex-direction: column; gap: 7px; }
.ai-left-line { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.ai-left-what { font-size: 13.5px; color: var(--ink); }
.ai-left-pct {
  font-size: 12.5px;
  color: var(--mut);
  /* Le percentuali non devono ballare da 5% a 100%. */
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.ai-left-track {
  display: block;
  width: 100%;
  height: 6px;
  border-radius: 999px;
  background: var(--track);
  overflow: hidden;
}
.ai-left-fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--accent);
}
/*
  E il colore avvisa, in tre gradini e non con una sfumatura: il punto di un colore qui e' che si
  noti senza essere letto, e una tinta che scivola dell'uno per cento alla volta e' una tinta che
  nessuno nota. Il giallo a settanta, il rosso a novanta - dove "ne resta parecchio" ha smesso di
  essere vero per la riserva di una giornata.
*/
.ai-left-fill--low { background: var(--warn); }
.ai-left-fill--spent { background: var(--danger); }
/* Una volta sola, sotto tutte e due: le riserve si riazzerano insieme. */
.ai-left-reset { font-size: 12px; line-height: 1.3; color: var(--mut2); }
.ai-left .explain-body { margin-top: 10px; }

/* The greeting closes the hero, so it needs air above it now. */
.hero .greet { margin: 6px 0 4px; }

/* The state sentence under the ring: narrow enough to read as a caption rather
   than as body copy running the full width of the page. */
.readiness-note {
  max-width: 40ch;
  margin: 8px auto 0;
  text-align: center;
  font-size: 13px;
  line-height: 1.6;
}

/* ---------- chat thread: day separators ---------- */
/*
  The thread has no height and no scrollbar of its own. It is a column of messages as tall as the
  conversation, and the page is what scrolls.

  It used to be a scroll container - a fixed dvh height with overflow-y: auto - and that was
  reported after using it: reading the chat on a phone meant dragging a small window inside a page
  that would not move, where a flick starting on a bubble did something different from a flick
  starting two centimetres lower. A nested scroller on a touch screen is a fault however carefully
  it is built.

  The height was there for a real reason and the reason still stands: a page scroller starts at the
  top, so a months-old conversation opens at its first message with the input far below the fold.
  That is now solved where it belongs, in app.js, which puts the page at the end on load and after
  every reply - the same thing it already did to the thread, pointed at the window instead.

  Nothing else embeds this fragment, so there is one arrangement rather than a default and an
  override. If something ever does embed it, it will need a height of its own and app.js will need
  to be told which element scrolls.
*/
/* Day separator: a centred label with a rule running through it. */
.chat-day + .chat-day { margin-top: 4px; }
.day-sep {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 14px 0 10px;
  font-family: var(--cond);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 2.4px;
  text-transform: uppercase;
  color: var(--mut2);
}
.day-sep::before, .day-sep::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line-soft);
}
.chat-day:first-child .day-sep { margin-top: 2px; }

/* Time of a message, beside the sender. Quieter than the name: it is there when
   looked for and out of the way when not. */
.bubble-who { display: flex; align-items: baseline; gap: 8px; }
.bubble-time {
  font-family: var(--cond);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 1.2px;
  color: var(--mut2);
}

/* An optimistically added bubble, before the server has confirmed it. Slightly
   faded so the difference is visible without being alarming; the swap that brings
   the real thread back replaces it. */
.bubble-pending { opacity: .55; }

/* ---------- native dropdowns and pickers ---------- */
/*
  A <select> popup is drawn by the browser on its own surface, not inside the
  card, so it does not inherit the page's background - it composites whatever
  background-color the control actually has. Ours was rgba(255,255,255,.03): a
  nearly transparent *white*, which looks dark over a dark card and renders as a
  white sheet in the popup. The closed control and the open list looked like they
  belonged to two different applications.

  The fix is an opaque colour plus explicitly styled options. Both come from
  tokens, so the light theme gets a light popup without another rule.
*/
select {
  background-color: var(--panel);
  background-image: none;
}
select option {
  background-color: var(--panel);
  color: var(--ink);
}

/*
  color-scheme tells the browser which way to draw the parts we cannot style at
  all: the date picker's calendar, the scrollbar inside a long dropdown, the
  spinner on a number field. It is set on :root, but `appearance: none` stops some
  browsers applying it to the widget, so it is repeated where it is needed.
*/
input, select, textarea { color-scheme: inherit; }
/* The calendar icon is drawn in black by default and vanishes on a dark field. */
:root:not([data-theme="light"]) input[type="date"]::-webkit-calendar-picker-indicator,
:root:not([data-theme="light"]) input[type="time"]::-webkit-calendar-picker-indicator {
  filter: invert(1) opacity(.65);
}

/* A validation message added by app.js reads exactly like a server-rendered one:
   the athlete should not be able to tell which side rejected their input. */
.field-error-live { animation: field-error-in .16s ease-out; }
@keyframes field-error-in {
  from { opacity: 0; transform: translateY(-2px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .field-error-live { animation: none; }
}
/* The offending control gets a red edge, so the message and the field agree. */
[aria-invalid="true"] { border-color: rgba(255, 92, 92, .55); }

/* ---------- tracker: plan vs done ----------
   A bare "10" beside an editable "10" reads as the same number twice, which is what
   made the tracker look like it was editing the schedule rather than logging today.
   The labels are what tell the two apart. */
.set-label {
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--mut2);
}
.session-set-plan { display: inline-flex; align-items: baseline; gap: 6px; }
.session-set-actual { display: inline-flex; align-items: baseline; gap: 6px; }
/* Full width so the row of three inputs sits underneath its own heading. */
.set-label-done { flex: 0 0 100%; color: var(--accent-text); margin-bottom: 2px; }

/* "Salvata", for the second or two after a note saves itself. Nothing else on the
   page changes on blur, so without this the athlete has no way to know it worked. */
.note-saved {
  display: inline-block;
  margin-top: 8px;
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--accent-text);
}

/*
  ---------- "scheda salvata", "esercizio salvato" ----------

  Asked for from production: "quando salvo un esercizio o una scheda deve apparire 'esercizio
  salvato', 'scheda salvata'". Both saves answer with the same fields the athlete had just filled
  in, so a page that saved and a page that did not are pixel for pixel the same - the only way to
  find out which had happened was to leave and come back.

  Written by the server into the fragment, not by a script: both actions swap the markup they are
  in, so anything the browser had put on screen would be swapped away with it.

  It leaves on its own after a few seconds. A confirmation that stays is furniture within a minute,
  and the athlete stops reading it - which is the same as not having it. In ok green rather than the
  accent, because the accent is what marks things to act on and this is not one.
*/
/*
  ---------- "scheda salvata", in cima e non di fianco ----------

  Stava accanto al bottone, in una colonna larga due parole, spezzata su due righe e sotto il pollice
  che aveva appena premuto - cioe' esattamente dove non si guarda dopo aver premuto, e dove il dito
  la copre. Chiesto come un toast in alto, ed e' il posto giusto: la conferma di un'azione si legge
  dove la pagina comincia, non dove finisce.

  position: fixed e non sticky: la conferma non appartiene alla barra di salvataggio ne' alla riga
  dell'esercizio - viene da li' nel markup solo perche' e' il server a scriverla dentro il frammento
  che rimanda indietro, ed e' li' che deve stare per sopravvivere allo scambio. A schermo e' un
  avviso della pagina.

  Sotto la barra in alto, non sopra: quella e' fissa e la coprirebbe.
*/
/*
  One success language everywhere. Page-level saves used .alert-success while the plan builder and
  exercise tracker used .saved-flash, so the same completed action moved around and changed colour
  between screens. All transient save confirmations now share the plan builder's green top toast.
  z-index 100 is above the header, drawers and overlays in this stylesheet (the next highest is 41).
*/
.alert-success,
.note-saved,
.saved-flash {
  position: fixed;
  top: calc(var(--bar-h) + 10px);
  left: 12px;
  right: 12px;
  z-index: 100;
  width: fit-content;
  max-width: calc(100% - 24px);
  margin: 0 auto;
  padding: 13px 18px;
  border: 1px solid color-mix(in srgb, var(--ok) 45%, transparent);
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--ok) 14%, var(--panel));
  box-shadow: var(--shadow-panel);
  font-size: 15px;
  font-weight: 600;
  text-align: center;
  color: var(--ok);
  pointer-events: none;
  animation: saved-fade 4s ease-out forwards;
}
@keyframes saved-fade {
  0% { opacity: 0; transform: translateY(-8px); }
  8%, 65% { opacity: 1; transform: none; }
  100% { opacity: 0; transform: translateY(-8px); }
}
/* For anybody who has asked for less movement it simply stays: a message that never fades is a
   smaller imposition than one that does, and this one is three words. */
@media (prefers-reduced-motion: reduce) {
  .alert-success,
  .note-saved,
  .saved-flash { animation: none; }
}

/* Exercise names already stored in lower case keep displaying with a capital, without a
   migration. New ones are capitalised on the way in (TrainingPlanService#exerciseName);
   this covers what is already in the database. ::first-letter, not text-transform:
   capitalize, which would make "flat bb press" into "Flat Bb Press" and "RDL" into "Rdl". */
.plan-builder-exercise h3::first-letter,
.session-exercise h3::first-letter { text-transform: uppercase; }

/* The × that removes a logged workout. Deliberately quiet and at the end of the row:
   destructive, so it must be reachable but never the thing the thumb lands on when the
   athlete meant to open the workout. */
/*
  Deleting a workout from the history.

  The × belongs in the card's own top-right corner. Laid out as a column beside the card it wrapped
  below on a phone, and a circle sitting under a card reads as a control belonging to nothing -
  reported from a live screen, where it looked like a button between two workouts.

  Absolutely positioned rather than moved into the card, because the card is a link and a form inside
  an anchor is not valid markup. The link gets room on its right so a long title never runs under it.
*/
.history-delete { flex: 0 0 auto; margin: 0; align-self: center; }
/*
  ---------- a record, marked ----------

  The trophy and the distance, on the row of the workout that holds the athlete's best time. Quiet on
  purpose: it is a reward, not a warning, and a list where one row shouts is a list that reads as an
  error. The accent's own colour with a soft fill, so it belongs to the palette and follows the one the
  athlete chose.

  The word travels with the icon - never the trophy alone. Recognising a shape is not the same as being
  told, and this is the kind of detail an athlete will want to be sure of.
*/
.history-pb {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-top: 6px;
  padding: 2px 8px 2px 6px;
  border-radius: 999px;
  border: 1px solid var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-family: var(--cond);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 1.1px;
}
.history-pb .ico { width: 14px; height: 14px; }

.history-row { position: relative; }
.history-row .history-delete {
  position: absolute;
  top: 2px;
  right: 2px;
  /* The size is stated, not shrink-wrapped. Absolutely positioned it should hug its 44px button, and
     it did not - it came out as wide as the card, putting the × over the title. Two numbers cost
     nothing and cannot be argued with. */
  width: 44px;
  height: 44px;
}
.history-row .plan-card-link { padding-right: 48px; }
/* Quiet in the corner and still a 44px target: the circle only appears under a finger or a cursor.
   A bordered button in every card corner competes with the workout for attention, and the workout is
   what the page is for. */
.history-row .history-delete .rbtn { border-color: transparent; background: transparent; }
.history-row .history-delete .rbtn:hover,
.history-row .history-delete .rbtn:focus-visible {
  border-color: var(--accent-line);
  background: var(--surface-2);
}

/* ---------- last session card ----------
   Four figures in a 2x2 grid: each one has room to be read at arm's length, which a row of
   four never does on a phone. */
/*
  Where a session came from - STRAVA, and one day GARMIN and the rest.

  Tiffany, and fixed rather than following the athlete's accent. It is not a control and it is not
  about them: it says who recorded this workout, and a badge in the same pink as every button on the
  page reads as something to press. The same reasoning as the training icons, which is also why it is
  the same colour as them.
*/
/*
  Both pills in the session's right-hand column, and they are one rule because they have to be exactly
  the same height. Davide asked for that in those words - "esattamente alto quanto il bottone del
  dispositivo" - and two rules with the same numbers written twice is two rules that drift apart the
  first time one of them is touched. Everything that decides the height lives here; only the colour
  differs below.
*/
.badge-synced,
.badge-delete {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--cond);
  font-size: 12px;
  line-height: 1.2;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  border-radius: 999px;
  padding: 4px 10px;
  white-space: nowrap;
}
.badge-synced {
  color: var(--tiffany);
  border: 1px solid var(--tiffany-line);
}
/*
  "Aggiunto": an exercise the schedule did not have, added during the workout.

  It shares the pill above rather than restating its numbers, for the reason written there. The
  accent colour, not the Tiffany one: Tiffany means "this came from somewhere else" - a device, a
  file - and this came from the athlete, in the gym, a minute ago.
*/
.badge-added {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--cond);
  font-size: 12px;
  line-height: 1.2;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  border-radius: 999px;
  padding: 4px 10px;
  white-space: nowrap;
  color: var(--accent-text);
  border: 1px solid var(--accent-line);
}
/* The delete: the same pill in red, and it asks before it does anything. */
.badge-delete {
  width: auto;
  margin: 0;
  /* The tokens the rest of the application already uses for danger, so this follows the theme
     rather than carrying two colours of its own. */
  color: var(--danger-ink);
  background: none;
  border: 1px solid var(--danger);
  cursor: pointer;
}
.badge-delete:hover { color: var(--danger); border-color: var(--danger); }
/* The column itself: the badge, then the delete under it, both to the right edge. */
.session-source {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
}
.session-source .session-delete { margin: 0; display: block; }
.last-session-when { font-size: 13px; }
.metric-grid {
  list-style: none;
  margin: 14px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.metric {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 12px 14px;
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  min-width: 0;
}
.metric-label {
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.3px;
  text-transform: uppercase;
  color: var(--mut2);
}
.metric-value { font-size: 15px; font-variant-numeric: tabular-nums; }

/*
  The one figure on this card that is an opinion: BUONA, OTTIMA, SCARSA.

  In the same ink as the three figures beside it, not in the accent. It was accent-coloured, and the
  athlete's verdict was that it broke the continuity of the row: four tiles side by side are four
  readings of one session, and colouring one of them makes it read as a badge or a link rather than
  as the fourth thing to read. The size and the weight are what mark it out; the colour is what tied
  it back to the rest.

  No colour declaration at all, deliberately, so it inherits the card's ink and follows the light and
  dark themes the way every other value does.

  Condensed and letter-spaced like the other display type in the interface, so a five-letter word
  and a nine-letter one both look deliberate in a tile 45% of a phone wide.
*/
.metric-grade {
  font-family: var(--cond);
  font-size: 19px;
  font-weight: 700;
  letter-spacing: 1.4px;
}
/* While the coach is deciding. Small and quiet: it sits beside three figures that are already
   readable, and it is replaced by the word itself a moment later. */
.metric-grade-wait { font-size: 12px; line-height: 1.35; }


/* ---------- nutrition monitor ----------
   The one place in the application where an icon is not monochrome. Carbohydrate, protein,
   fat and hydration are four quantities read at a glance, and a colour tells them apart
   faster than a word does. Defined here and nowhere else: the template carries a class per
   row and names no colour at all. */
.macro-carbs  { --macro: var(--accent); }
.macro-protein { --macro: #2ee6ff; }
.macro-fat     { --macro: #ffc233; }

.nutrition-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-top: 12px;
}
.nutrition-title { font-size: 16px; font-weight: 600; }
.nutrition-kcal { flex: 0 0 auto; font-variant-numeric: tabular-nums; }
.nutrition-kcal-value { color: var(--accent-text); font-size: 20px; font-weight: 600; }
.nutrition-kcal-target {
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.3px;
  color: var(--mut2);
}
.nutrition-no-target { margin: 8px 0 0; font-size: 12.5px; }

/* ---------- nutrition as an accordion ---------- */
/* The tallest card on the dashboard, and most visits are not about eating - so it collapses to one
   line. The summary carries the heading and today's energy, which is the figure somebody opening the
   app wants without expanding anything. */
.nutrition-accordion > summary {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  cursor: pointer;
  list-style: none;
}
.nutrition-accordion > summary::-webkit-details-marker { display: none; }
/* A triangle drawn in CSS rather than the UA marker, so it can sit after the heading and rotate. */
.nutrition-accordion > summary h3::after {
  content: '';
  display: inline-block;
  margin-left: 8px;
  border: 5px solid transparent;
  border-top-color: var(--mut);
  transform: translateY(3px);
}
.nutrition-accordion[open] > summary h3::after {
  border-top-color: var(--accent-text);
  transform: translateY(-1px) rotate(180deg);
}
.nutrition-collapsed {
  font-family: var(--cond);
  font-size: 15px;
  letter-spacing: 1px;
  color: var(--accent-text);
}
/* Inside the accordion the explainer is a row, not a card heading. */
.nutrition-explain > summary { display: flex; align-items: center; gap: 8px; cursor: pointer; }

/* ---------- the athlete's own ceilings ---------- */
/* Closed by default and styled like the other explainers, so the card still reads as "today's
   eating" rather than a settings screen. The badge appears only once a figure has been overridden -
   the athlete should be able to tell at a glance whether a bar is measuring their number or ours. */
.macro-edit { margin-top: 14px; }
/*
  This is a control, so it looks like one.

  It was grey uppercase 13px on a dark card - the same treatment as the section labels around it -
  and the athlete who wanted 180 g of protein instead of the computed 160 reported that they could
  not change it. They could: this opens four number boxes that accept up to 500 g, and have done
  since the card was built. It simply did not read as something you could press, sitting under five
  progress bars in the styling of a caption.

  Accent text inside a bordered pill, at the same height as every other tap target. Nothing behind
  it changed; the athlete can now see it is there.
*/
.macro-edit > summary {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  padding: 0 14px;
  border: 1px solid var(--accent-line);
  border-radius: 999px;
  font-family: var(--cond);
  font-size: 13px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--accent-text);
}
.macro-edit > summary:hover { background: var(--accent-soft); }
.macro-edit-custom {
  border: 1px solid var(--accent-line);
  border-radius: 999px;
  padding: 1px 8px;
  font-size: 12px;
  letter-spacing: 1px;
  color: var(--accent-text);
}
.macro-edit-form { margin-top: 12px; }
.macro-edit-form input { margin-bottom: 10px; }
.macro-edit-hint { margin: 2px 0 14px; font-size: 12.5px; line-height: 1.5; }

.macro-list { list-style: none; margin: 16px 0 0; padding: 0; }
/* Two rows in a three-column grid: icon, name, amount on the first; the track spans all
   three on the second. A bar under its own label is easier to scan down a column than one
   squeezed to the right of it. */
.macro {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 6px 10px;
  padding: 9px 0;
}
.macro + .macro { border-top: 1px solid var(--line-soft); }
.macro-icon {
  width: 10px;
  height: 10px;
  border-radius: 3px;
  background: var(--macro);
}
.macro-name { font-size: 14px; min-width: 0; }
.macro-amount {
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.1px;
  color: var(--mut);
  font-variant-numeric: tabular-nums;
}
.macro-track {
  grid-column: 1 / -1;
  height: 4px;
  border-radius: 999px;
  background: var(--field);
  overflow: hidden;
}
/*
  ---------- le colonne dei grafici crescono dal basso ----------

  "Anima anche queste colonne, devono crescere in 1 secondo, solo quando sono circa al centro dello
  schermo... animate solo la prima volta che le vedi o quando ricarichi o torni in home, non
  sempre."

  Le due carte a colonne - i sette giorni e il volume settimanale - salgono da terra in un secondo.
  Come per le barre dei macro l'altezza resta quella della classe che il server scrive, e
  l'animazione va da zero a quella: senza script le colonne sono semplicemente alte come devono.

  L'origine e' il fondo, dichiarata: senza, una colonna crescerebbe dal proprio centro, cioe' verso
  l'alto E verso il basso attraverso la linea di base del grafico.

  Una volta per apertura, non a ogni passaggio: chi guarda scorre su e giu' per la stessa pagina, e
  colonne che ripartono ogni volta smettono di essere un ingresso e diventano un tic. Se ne occupa
  app.js, che smette di osservare l'elemento appena la classe e' messa - ricaricare o rientrare in
  home sono pagine nuove, e li' ricominciano.
*/
.day-fill, .volume-fill { transform-origin: bottom center; }
@media (prefers-reduced-motion: no-preference) {
  .day-bars.is-visible .day-fill,
  .volume-bars.is-visible .volume-fill {
    animation: colonna-cresce 1s cubic-bezier(.2, .75, .3, 1);
  }
}
/* Solo il "from", come l'anello e le barre: il valore d'arrivo e' quello calcolato sull'elemento. */
@keyframes colonna-cresce { from { transform: scaleY(0); } }

/* Una etichetta per volta sulla piega dei pasti: il conto quando e' chiusa, "riduci" quando e'
   aperta. Stessa regola della piega del consiglio, scritta accanto a cio' che governa. */
.meal-more .meal-more-close,
.meal-more[open] .meal-more-open { display: none; }
.meal-more[open] .meal-more-close { display: inline; }

.macro-fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--macro);
  /* Cresce dal proprio bordo sinistro. Senza un'origine dichiarata si allargherebbe dal centro,
     che su una barra di progresso e' il movimento sbagliato: dice "si sta gonfiando" invece di
     "si sta riempiendo". */
  transform-origin: left center;
}
/*
  ---------- e le barre si riempiono ----------

  "Le linee del monitor nutrizionale devono crescere da sx verso dx con le stesse tempistiche
  dell'anello all'inizio - portale a 1 sec tutte", poi "falle caricare in due secondi anziche' in un
  secondo, ma solo quelle", e infine "aumenta tempo a 2,5 secondi".

  Due secondi e mezzo, quindi, mentre l'anello resta a uno. La stessa curva pero' si', perche' e' quella a
  dare il carattere del movimento: quello che cambia e' che una barra ha una lunghezza da percorrere e
  la si guarda percorrerla, mentre l'anello e' un numero che si posa.

  Tutte e quattro insieme: sono i quattro pezzi di una giornata sola, e sfalsarle direbbe che uno
  viene prima dell'altro.

  La classe la mette app.js quando la lista entra nello schermo - la scheda alimentare sta sotto alla
  piega, e una barra che si riempie mentre nessuno la guarda e' una barra che nessuno vede riempirsi.
  Senza script la classe non arriva, non c'e' nessuna animazione, e la barra e' piena come e' sempre
  stata: e' per questo che si aggiunge una regola invece di toglierla.
*/
@media (prefers-reduced-motion: no-preference) {
  .macro-list.is-visible .macro-fill { animation: macro-grow 2.5s cubic-bezier(.2, .75, .3, 1); }
}
/* Solo il "from": il valore d'arrivo e' quello calcolato sull'elemento, cioe' la larghezza che la
   classe fill-N gli ha dato. Come per l'anello, e' l'unico modo di animare fino a un numero che il
   foglio di stile non conosce. */
@keyframes macro-grow { from { transform: scaleX(0); } }
/* Width from a class, never a style attribute: script-src/style-src 'self' with no
   'unsafe-inline' blocks an inline style, and every bar would sit at zero. Five per cent
   steps are finer than the eye reads on a 4px bar.

   Non piu' legate a .macro-fill: le usa anche la riserva giornaliera nel menu, ed erano l'unica
   cosa che avrebbe dovuto essere scritta due volte. Una famiglia di larghezze per tutta
   l'applicazione, e chi ne aggiunge una terza non deve copiare ventuno righe. */
.fill-0 { width: 0%; }
.fill-5 { width: 5%; }
.fill-10 { width: 10%; }
.fill-15 { width: 15%; }
.fill-20 { width: 20%; }
.fill-25 { width: 25%; }
.fill-30 { width: 30%; }
.fill-35 { width: 35%; }
.fill-40 { width: 40%; }
.fill-45 { width: 45%; }
.fill-50 { width: 50%; }
.fill-55 { width: 55%; }
.fill-60 { width: 60%; }
.fill-65 { width: 65%; }
.fill-70 { width: 70%; }
.fill-75 { width: 75%; }
.fill-80 { width: 80%; }
.fill-85 { width: 85%; }
.fill-90 { width: 90%; }
.fill-95 { width: 95%; }
.fill-100 { width: 100%; }

/* ---------- log a meal ----------
   One row: camera, description, send. The camera is a label wrapping a hidden file input,
   so tapping the square opens it with no script at all. */
/*
  Logging a meal, folded behind a "+".

  Closed it is one row: a round plus and the words, 44px tall because that is the thumb minimum.
  Open it is the camera, the field, the send arrow and what it costs, unchanged - the fold is the
  only thing that changed, and the form inside still posts with HTMX or without it.
*/
.meal-log { margin-top: 18px; }
.meal-log > summary {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  cursor: pointer;
  list-style: none;
  font-family: var(--cond);
  font-size: 13px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--ink);
}
.meal-log > summary::-webkit-details-marker { display: none; }
.meal-log > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The plus turns into a minus when it is open, so the control says what pressing it does. */
.meal-log-plus {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 1px solid var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent-text);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
  line-height: 0;
  padding-bottom: 2px;
}
.meal-log[open] .meal-log-plus::after { content: "\2212"; }
.meal-log:not([open]) .meal-log-plus::after { content: "+"; }
.meal-form {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
}
.meal-photo {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin: 0;
  border: 1px solid var(--accent-line);
  border-radius: 12px;
  color: var(--accent-text);
  cursor: pointer;
}
.meal-photo:hover { background: var(--accent-soft); }
.meal-photo input[type="file"] {
  /* Not display:none: a hidden input is not focusable, which would drop keyboard access to
     the camera entirely. Visually hidden, still reachable by tab. */
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
}
.meal-form input[type="text"] { flex: 1 1 auto; min-width: 0; margin: 0; }
.meal-send {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: none;
  border-radius: 999px;
  background: var(--accent);
  color: #12000a;
  cursor: pointer;
}
.meal-send:hover { filter: brightness(1.08); }
/*
  "Analizza foto", the same size as the arrow beside it.

  It shipped as a full-width primary button, which on a 390px row squeezed the camera, the text field
  and the arrow into what was left - Davide's screenshot shows the three of them crushed against a
  pink slab. Same height as the send button and only as wide as its label needs: it is one more
  control in the row, not a new row.
*/
.meal-analyse {
  flex: 0 0 auto;
  /* width and margin restated, because .btn sets width: 100% and a top margin - written for the
     full-width buttons that are most of them. Without these two the button kept the whole row and
     the camera, the field and the arrow were crushed against it, which is the screenshot Davide
     sent. */
  width: auto;
  margin: 0;
  height: 44px;
  min-height: 44px;
  padding: 0 12px;
  font-size: 12px;
  letter-spacing: .8px;
  white-space: nowrap;
}

.meal-list { list-style: none; margin: 14px 0 0; padding: 0; }
.meal-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
  padding: 9px 0;
  border-top: 1px solid var(--line-soft);
}
.meal-row-main { min-width: 0; }
.meal-row-text { display: block; font-size: 14px; }
/* The meal and what it cost, on one line: the name takes the room it needs, the calories sit at the
   right where a list of them can be scanned down. */
.meal-row-line { display: flex; align-items: baseline; gap: 10px; }
.meal-row-line .meal-row-text { flex: 1 1 auto; min-width: 0; }
.meal-row-kcal {
  flex: 0 0 auto;
  font-family: var(--cond);
  font-size: 13px;
  color: var(--accent-text);
  white-space: nowrap;
}
/* Under it, the context. No longer uppercase: it was three uppercase items under every meal of the
   day, which reads as three labels rather than as an aside - and uppercase is what you set a short
   label in, not a line of running text. */
.meal-row-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 2px;
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: .4px;
  color: var(--mut2);
}
.meal-row-source { color: var(--mut); }
/* Free, and said in the accent so it reads as a small win rather than another grey label. The
   athlete is being asked to economise on AI interactions; this is the only place the application
   can show them what economising looked like. */
.meal-row-reused { color: var(--accent-text); }

/* The pencil and the cross, side by side. The pencil first: correcting is the common act and
   deleting the rare one, because an estimate off a photograph is usually wrong in one number. */
.meal-row-actions { display: flex; align-items: flex-start; gap: 2px; flex: 0 0 auto; }
.meal-edit > summary {
  display: flex;
  align-items: center;
  justify-content: center;
  /* 44px of target around a 15px icon: this sits beside a delete button, and a mis-tap here costs
     the athlete the entry they were trying to fix. */
  min-width: 44px;
  min-height: 44px;
  color: var(--mut);
  cursor: pointer;
  list-style: none;
}
.meal-edit > summary::-webkit-details-marker { display: none; }
.meal-edit > summary:hover { color: var(--accent-text); }
.meal-edit[open] > summary { color: var(--accent-text); }
/* Out of the row and across the card once open: six labelled boxes cannot live in a column beside
   a description, and squeezing them there is how a correction form becomes unusable on a phone. */
.meal-edit[open] {
  position: relative;
  z-index: 1;
}
.meal-edit-form {
  position: absolute;
  right: 0;
  top: 44px;
  width: min(300px, calc(100vw - 64px));
  display: grid;
  gap: 6px;
  padding: 12px;
  border: 1px solid var(--accent-line);
  border-radius: 12px;
  /*
    --panel, and it used to say --card. There is no --card token in this stylesheet and there never
    has been, so the declaration resolved to nothing and the panel was transparent: reported with a
    screenshot of the correction form floating over the meal list, both legible and neither readable.
    --panel is the token the other floating surfaces use, and it is opaque for exactly this reason.
  */
  background: var(--panel);
  box-shadow: 0 12px 32px rgba(0, 0, 0, .55);
  text-align: left;
}
.meal-edit-form label {
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.1px;
  text-transform: uppercase;
  color: var(--mut2);
}
/* One field now, and it is a textarea: a meal reads "150 g di carne di manzo grigliata e 100 g di
   hamburger di pollo", which a one-line box on a 390px phone showed about half of - and the half it
   cut off was the end, where the second food and its weight are. resize: vertical because the panel
   is absolutely positioned and a sideways drag would take the field out past its own edge. */
.meal-edit-form input,
.meal-edit-form textarea { width: 100%; }
.meal-edit-form textarea {
  resize: vertical;
  min-height: 72px;
  font: inherit;
  line-height: 1.4;
}
.meal-edit-hint { margin: 2px 0 0; font-size: 12px; }

/* What logging costs, under the input that spends it. Small and grey: it is a standing fact about
   the control above, not a warning about what just happened. */
.nutrition-cost { margin: 6px 0 0; font-size: 12px; line-height: 1.45; }
/*
  The coach's sentence about the meal just logged. Shown once, on the swap that logged it, and never
  again - which is why it is styled as something that arrived rather than as part of the card: a left
  rule in the accent, the same shape as the coach's boxes elsewhere.
*/
.meal-advice {
  margin: 10px 0 0;
  padding: 8px 10px;
  border-left: 2px solid var(--accent);
  border-radius: 0 8px 8px 0;
  background: var(--field);
  font-size: 13px;
  line-height: 1.5;
  color: var(--txt);
}
.box-ask-cost {
  display: block;
  margin-top: 10px;
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.1px;
  text-transform: uppercase;
}

/* The day the counter is about. Above the figures and in the conditional face, because it is a
   label for everything under it rather than a line of prose - and because a day of eating that runs
   05:00 to 05:00 needs naming, or at one in the morning the card looks stuck. */
.nutrition-dayname {
  margin: 12px 0 0;
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--mut2);
}

/* A compact door from today's consumed totals to what the athlete planned to eat. The whole row is
   one touch target; the second line explains the destination before navigation. */
.nutrition-plan-shortcut {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    "title arrow"
    "hint arrow";
  align-items: center;
  gap: 2px 12px;
  min-height: 56px;
  margin: 7px 0 2px;
  padding: 9px 12px;
  border: 1px solid var(--accent-line);
  border-radius: 12px;
  background: var(--accent-soft);
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}
.nutrition-plan-shortcut:hover {
  border-color: var(--accent-text);
  text-decoration: none;
}
.nutrition-plan-shortcut:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
.nutrition-plan-shortcut-title {
  grid-area: title;
  color: var(--accent-text);
  font-size: 14.5px;
  font-weight: 700;
}
.nutrition-plan-shortcut-hint {
  grid-area: hint;
  color: var(--mut);
  font-size: 11.5px;
  line-height: 1.35;
}
.nutrition-plan-shortcut-arrow {
  grid-area: arrow;
  color: var(--accent-text);
  font-size: 20px;
  transition: transform 120ms ease;
}
.nutrition-plan-shortcut:hover .nutrition-plan-shortcut-arrow { transform: translateX(3px); }
@media (prefers-reduced-motion: reduce) {
  .nutrition-plan-shortcut-arrow { transition: none; }
}

/* ---------- the week behind ---------- */
.nutrition-history { margin-top: 14px; }
.nutrition-history > summary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--mut);
  cursor: pointer;
}
.nutrition-history-list { list-style: none; margin: 4px 0 0; padding: 0; }
.nutrition-history-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: 7px 0;
  border-top: 1px solid var(--line-soft);
  font-variant-numeric: tabular-nums;
}
.nutrition-history-day {
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--mut2);
}
.nutrition-history-empty { font-size: 12px; color: var(--mut2); }
.nutrition-history-figures { display: flex; flex-wrap: wrap; gap: 10px; font-size: 12.5px; }
.nutrition-history-kcal { font-weight: 600; }
/* The three macros keep the colours they have on the bars above, so a column of numbers reads as
   the same three quantities without repeating their names seven times. */
.nutrition-history-figures .macro-carbs,
.nutrition-history-figures .macro-protein,
.nutrition-history-figures .macro-fat { color: var(--macro); }

/* ---------- goals ---------- */
.goal-list { list-style: none; margin: 4px 0 0; padding: 0; }
.goal { padding: 10px 0; border-bottom: 1px solid var(--line-soft); }
.goal:last-child { border-bottom: none; }
.goal-pick {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  font-family: var(--sans);
  font-size: 14.5px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink);
  cursor: pointer;
}
.goal-pick input { width: auto; margin: 0; accent-color: var(--accent-text); }
/* Indented under the label it belongs to, and in the flow rather than revealed by script: a
   field that only exists when JavaScript runs is a field that does not exist for everybody. */
.goal-target { display: block; margin: 10px 0 0 30px; }
/* Il peso target adesso e' un campo della carta e non piu' il dettaglio di una riga: sta sotto le
   caselle, allineato al loro testo, con un filo d'aria sopra che lo stacca dall'ultima.

   Il nome non e' .weight-target perche' quello esiste gia' - e' la didascalia della pagina del peso,
   tremila righe piu' in giu' - e due regole con lo stesso nome sono due comportamenti che dipendono
   da quale si legge per prima. StylesheetClashTest l'ha rifiutato, ed e' esattamente il suo lavoro. */
.goal-target-weight {
  margin: 16px 0 0 30px;
  padding-top: 14px;
  border-top: 1px solid var(--line-soft);
}
.goal-target label { margin: 0 0 6px; }
.goal-target input { width: 140px; margin: 0; }
.goal-unit {
  margin-left: 8px;
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--mut2);
}
.goal-hint { display: block; margin-top: 6px; font-size: 12.5px; }
.goals-reminder { margin-top: 12px; font-size: 13px; }

/* The boundary of what this application does, so it reads as a statement and not as a notice
   to be dismissed: no close button, no accent colour, nothing that looks interactive. */
.disclaimer {
  border-color: var(--line-soft);
  background: none;
}
.disclaimer p { margin: 0; font-size: 12.5px; line-height: 1.55; color: var(--mut); }

/* ---------- start again ---------- */
/*
  Folded, and it looks folded. The one irreversible control on the profile page should take a
  deliberate tap to reveal, and should not be the loudest thing on a page whose other two sections
  are "your weight" and "change your password".
*/
.danger-zone > summary {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  cursor: pointer;
  list-style: none;
  font-family: var(--cond);
  font-size: 15px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--danger-ink);
}
.danger-zone > summary::-webkit-details-marker { display: none; }
.danger-zone > summary::before {
  content: "\203A";
  font-size: 17px;
  line-height: 1;
  transition: transform .15s ease;
}
.danger-zone[open] > summary::before { transform: rotate(90deg); }
.danger-zone > summary:focus-visible { outline: 2px solid var(--danger); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
  .danger-zone > summary::before { transition: none; }
}
/* What goes and what stays, as two lines rather than a paragraph: this is the part somebody
   actually reads before pressing the button, and a list is read where prose is skimmed. */
.reset-list { list-style: none; margin: 12px 0; padding: 0; font-size: 14px; }
.reset-list li {
  padding: 8px 0 8px 14px;
  border-left: 2px solid var(--line);
  color: var(--ink-2);
  line-height: 1.5;
}
.reset-list li + li { margin-top: 6px; }

/*
  The note composer keeps clear of the bottom bar when the page scrolls to it.

  app.js brings this into view on load with block:'end', which aligns the bottom of the element
  with the bottom of the viewport - and the bottom of the viewport is where the floating bar is.
  Measured: the textarea sat half under it and the "remember this" button was off the screen
  entirely, so the page scrolled to the composer and then hid it.

  scroll-margin-bottom is the native answer and needs no arithmetic in the script: 120px is the
  bar's 68 plus the room it floats in plus a gap that leaves the button obviously tappable.
*/
.memory-add { scroll-margin-bottom: 120px; }

/* ---------- the schedule builder's one Save ---------- */
/*
  Sticky above the bottom bar. A schedule of five exercises is four or five screens, and a Save
  that lives at the end of it is a Save the athlete has to go and find after every change - which
  is most of what "my edits did not save" was actually about.

  bottom is the floating bar's height plus its gap, so the two never overlap; the safe-area inset
  keeps it clear of the home indicator on a phone without one.
*/
.plan-save-bar {
  position: sticky;
  bottom: calc(96px + var(--viewport-bottom, 0px) + env(safe-area-inset-bottom, 0px));
  z-index: 5;
  margin-top: 18px;
  padding: 10px 0;
}
/* Il bottone da solo occupa la riga: la conferma se n'e' andata in cima, e cio' che resta accanto
   e' il "sto salvando", che c'e' solo mentre la richiesta e' in volo. */
.plan-save-bar form {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  margin: 0;
  padding: 10px 12px;
  border-radius: 999px;
  /* Opaque, for the same reason the cards are: content scrolls underneath this. */
  background: var(--panel);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-panel);
}
/* Largo quanto la barra: era stretto perche' doveva dividere la riga con la conferma, e la
   conferma adesso e' in cima. */
.plan-save-bar .btn { margin-top: 0; flex: 1 1 auto; }
.plan-save-bar .muted { font-size: 13px; }

/* =========================================================================
   Card heads, from the Emergent prototype.

   One pattern for every section: a small condensed eyebrow saying what kind of
   thing this is, then the title with its icon, then anything that belongs on
   the right - a status, a source badge, a control.

   What the prototype does that the cards did not: it names the *category*
   above the *subject*. "ULTIMA SESSIONE / Run" reads at a glance where "Ultima
   sessione — Corsa" is one line to parse, and it lets the title be the thing
   the athlete actually cares about. The icon is decoration next to a word,
   never the word itself.

   Two colours, not three. The prototype pairs pink with a cyan; the accent
   here stays the athlete's single chosen one and the second colour is the
   page's own grey, which is what keeps four accents working instead of one.
   ========================================================================= */
.card-head { margin-bottom: 14px; }
.card-eyebrow {
  display: block;
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: var(--mut2);
  margin-bottom: 2px;
}
.card-title-row {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 34px;
}
.card-title-row h2,
.card-title-row h3 {
  margin: 0;
  font-size: 21px;
  font-weight: 800;
  letter-spacing: -.2px;
  min-width: 0;
}
/* The icon takes the accent; the words stay in the page's ink. Colour on the
   glyph and not on the text is what stops a wall of accent-coloured headings. */
.card-title-row > .ico {
  flex: 0 0 auto;
  width: 21px;
  height: 21px;
  color: var(--ico-tint, var(--accent));
}
.card-title-row > .card-head-end { margin-left: auto; flex: 0 0 auto; }

/* Every icon from fragments/icons, wherever it lands. */
.ico { width: 18px; height: 18px; flex: 0 0 auto; vertical-align: -3px; }

/* ---------- metric tiles with an icon on the label ---------- */
.metric-grid .metric-label,
.tile .tile-label {
  display: flex;
  align-items: center;
  gap: 6px;
}
.metric-label > .ico,
.tile-label > .ico {
  width: 13px;
  height: 13px;
  color: var(--ico-tint, var(--accent));
  opacity: .85;
}

/* ---------- rimosso: la pastiglia "online" ----------

   Veniva dal prototipo e non e' mai arrivata in una pagina: nessun template la nomina e nessuno la
   compone a runtime. Un pallino verde che dice "in linea" a un atleta che sta guardando la sua
   applicazione non risponde comunque a nessuna domanda. */

/* ---------- removed: the coach card ----------

   Three suggested questions, a line to type a fourth into and a button opening the chat used to
   sit under the hero, and the rules for all three were here. The card is gone and so are they.

   The reason is the count, not the card: the home had five separate ways to talk to the coach -
   this one, the line under the readiness comment, and an ask fold under each of the other boxes -
   so the first thing the page asked an athlete was which of five identical inputs to use. The chat
   now has two doors, the bottom bar and the line under the coach's own reading of the week, and
   .box-chat-ask below is what draws the second.
*/

/* The head inside a <summary> has to be a block, or the eyebrow and the title
   sit on one line beside the disclosure triangle. */
.nutrition-summary-head { display: block; flex: 1 1 auto; min-width: 0; margin-bottom: 0; }
.macro-name { display: flex; align-items: center; gap: 7px; min-width: 0; }
.macro-name > .ico { width: 14px; height: 14px; color: var(--mut); }
.set-label > .ico { width: 13px; height: 13px; color: var(--ico-tint, var(--accent)); }

/* ---------- what is left of the week ---------- */

/* Rest is the whole card when it applies, so it gets the weight of a statement
   rather than of a row. */
.next-rest {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  margin: 0;
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink-2);
}
.next-rest > .ico { flex: 0 0 auto; margin-top: 2px; color: var(--ico-tint, var(--accent)); }

/* ---------- rimosso: la spinta alla scheda ferma ----------

   "Non apri questa scheda da tre settimane", con il bottone per eseguirla, sotto il prossimo
   allenamento. La carta e' stata tolta ("drop the idle-schedule nudge") e le sue sei regole erano
   rimaste qui, insieme alle quattro chiavi che le davano le parole. Nessuna delle due meta' serviva
   piu' all'altra. */

/* ---------- the schedule library, as a list of titles ---------- */
/*
  Names and a count, and the detail behind a tap. The card used to print the
  first four exercises of every schedule with their reps and rests, which on
  three schedules was thirty lines of a dashboard that already has six other
  cards - the athlete scrolled past their whole training week to reach what
  they had eaten.
*/
.plan-list { list-style: none; margin: 12px 0 0; padding: 0; }
.plan-entry-row + .plan-entry-row { border-top: 1px solid var(--line-soft); }
.plan-entry > summary {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  min-height: 52px;
  padding: 12px 0;
  cursor: pointer;
  list-style: none;
}
.plan-entry > summary::-webkit-details-marker { display: none; }
.plan-entry-main { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 8px; min-width: 0; }
.plan-entry-name { font-size: 16.5px; font-weight: 700; color: var(--ink); }
/* The word carries the meaning and the accent only emphasises it, so this still
   reads for somebody who cannot tell the two colours apart. */
.plan-entry-last {
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--accent-ink);
  border: 1px solid var(--accent-line);
  border-radius: 999px;
  padding: 1px 8px;
  white-space: nowrap;
}
.plan-entry-count {
  flex: 0 0 auto;
  font-family: var(--cond);
  font-size: 12.5px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--mut2);
  white-space: nowrap;
}
/* A chevron that turns, so the row reads as openable before it is opened. */
.plan-entry > summary .plan-entry-count::after {
  content: " \203A";
  display: inline-block;
  color: var(--accent-text);
  transition: transform .15s ease;
}
.plan-entry[open] > summary .plan-entry-count::after { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) {
  .plan-entry > summary .plan-entry-count::after { transition: none; }
}

.plan-entry-exercises { list-style: none; margin: 4px 0 0; padding: 0 0 4px; }
.plan-entry-exercise {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 0 8px 12px;
  border-left: 2px solid var(--line);
}
.plan-entry-exercise-name { min-width: 0; font-size: 15px; color: var(--ink-2); }
.plan-entry-exercise-sets { flex: 0 0 auto; display: flex; align-items: baseline; gap: 8px; }
.plan-entry-reps { font-weight: 700; color: var(--accent-text); font-variant-numeric: tabular-nums; }
.plan-entry-rest { font-size: 12.5px; }
.plan-entry-empty { margin: 0 0 8px; }

.plan-entry-actions { display: flex; align-items: center; gap: 14px; margin: 6px 0 14px; }
.plan-entry-actions form { flex: 1 1 auto; margin: 0; }
.plan-entry-actions .btn { margin-top: 0; }

/* Two tiles now, not four, so they share the row the way the four used to share
   two. Full width for the percentage was tried and rejected: it pushed the
   energy tile into a single 130px column beside it, where "bruciate in
   allenamento" wraps onto two lines and the pair stops looking like a pair. */
.load-block .tile-label { align-items: flex-start; }

/*
  ---------- spostare un esercizio nella scheda ----------

  Due frecce accanto al titolo, della stessa taglia del comando che toglie l'esercizio: sono cose
  dello stesso genere - si fanno a una riga - e stare in una fila sola le fa leggere come un gruppo
  invece che come tre bottoni scollegati.

  Larghezza fissa e non automatica: una freccia e' un carattere solo, e senza larghezza i due
  bottoni ballano di qualche pixel a seconda del glifo.
*/
.exercise-move { display: inline-flex; gap: 4px; margin-left: auto; }
.exercise-move-btn {
  width: 38px;
  min-height: 38px;
  padding: 0;
  font-size: 16px;
  line-height: 1;
}

/*
  ---------- planned sessions ----------

  The agenda page, and the one line the dashboard borrows from it. The third kind of training this
  application holds: not a session that happened, not a routine with no date, but an intention with
  a day on it.

  Read as a list of days, not a table of fields. The question the page answers is "which day am I
  training and what am I doing", so the weekday is the loudest thing in a row and everything else
  sits under it.
*/
.planned-form { display: flex; flex-direction: column; gap: 2px; }
/* Two or three inputs abreast where they are short - a day beside a time - and wrapping rather than
   shrinking on the narrowest phones, because a date input squeezed below about 120px hides its own
   calendar button. */
/*
  Una riga di campi affiancati.

  gap su tutte e due gli assi, non solo su quello orizzontale: tre campi su uno schermo stretto
  vanno a capo, e senza spaziatura verticale la seconda riga finisce appiccicata alla prima.

  align-items: start, perche' i campi non devono stirarsi all'altezza del piu' alto: quello che
  conta e' che comincino tutti dallo stesso punto, che e' esattamente cio' che l'atleta guarda -
  "devono essere allineati, attualmente le input non lo sono".
*/
.planned-row { display: flex; flex-wrap: wrap; align-items: start; gap: 12px; }
.planned-field { display: block; flex: 1 1 130px; min-width: 130px; }
.planned-field input, .planned-field select { width: 100%; }
.planned-form .btn { margin-top: 16px; align-self: flex-start; }

.planned-list { list-style: none; margin: 0; padding: 0; }
.planned-item {
  padding: 14px 0;
  border-bottom: 1px solid var(--line);
}
.planned-item:last-child { border-bottom: none; }

/*
  ---------- today has an edge ----------

  A 3px accent bar down the left of the row whose day is today, and the same bar on the dashboard's
  next-session card when that session is today.

  It is the cheapest possible answer to "which of these is the one I have to do now": the eye finds a
  coloured edge before it reads a single date, and the agenda is seven rows of dates that all look
  alike. The badge saying "oggi" stays - the stripe is for finding the row, the badge is for being
  sure - so nothing here is colour on its own carrying meaning.

  Drawn with a border and negative padding rather than a pseudo-element, so it takes part in layout
  and cannot land on top of the text at any width.
*/
.planned-item.planned-is-today {
  border-left: 3px solid var(--accent);
  padding-left: 11px;
}
/* On the card the bar joins the border it already has, so only the left edge changes weight. */
.next-planned.planned-is-today {
  border-left-width: 3px;
  border-left-color: var(--accent-text);
  padding-left: 12px;
}

/* The day line. The weekday carries the weight; the date and the hour are its detail. */
.planned-when { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 8px; }
.planned-day {
  font-family: var(--cond);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--ink);
}
.planned-date, .planned-time { font-family: var(--cond); font-size: 13px; color: var(--mut2); }
/* "oggi" / "domani". In the accent, because on this page they are the only thing worth spotting
   from across the room. */
.planned-near {
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--accent-text);
}

.planned-what { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px; margin-top: 4px; }
.planned-sport { font-size: 15.5px; font-weight: 600; color: var(--ink); }
.planned-figure, .planned-plan { font-family: var(--cond); font-size: 13px; color: var(--mut2); }
.planned-note { font-size: 13px; margin: 4px 0 0; }

/*
  What became of it. Three states and three different jobs: done is reassurance, skipped is a fact,
  and "planned and never recorded" is the one the athlete has to do something about - so it is the
  only one given a colour.
*/
.planned-status {
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--mut2);
  margin: 6px 0 0;
}
.planned-status-done { color: var(--ok); }
/* --danger-ink rather than --danger: an unrecorded session is a nudge, not an error, and the two
   tokens exist precisely so a warning can be legible without being alarming. Both are redefined for
   the light theme, so this follows the theme rather than pinning a colour of its own. */
.planned-status-missed { color: var(--danger-ink); }
/* A finished session recedes; the ones still to do are what the page is for. */
.planned-done, .planned-skipped { opacity: .68; }

.planned-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.planned-actions form { margin: 0; }
.planned-edit { margin-top: 10px; }
.planned-edit summary {
  font-family: var(--cond);
  font-size: 12.5px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--mut2);
  cursor: pointer;
}
.planned-edit form { margin-top: 8px; }

/*
  The dashboard's one line from all this, above the weekly shortfall: the more specific answer to
  the same question. A link rather than a card of its own - the agenda is where anything is done
  about it.
*/
.next-planned {
  display: block;
  text-decoration: none;
  padding: 12px 14px;
  /* Dodici sopra e non zero: qui sopra c'e' il verdetto della settimana, che e' un riquadro con un
     bordo suo, e i due si toccavano - due scatole attaccate si leggono come una scatola sola divisa
     male. Sotto ce n'erano gia' quattordici e restano: e' lo stesso stacco, adesso da tutt'e due i
     lati. */
  margin-top: 12px;
  margin-bottom: 14px;
  border-radius: 12px;
  border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.next-planned-eyebrow {
  display: block;
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--accent-text);
}
.next-planned-when { display: block; font-size: 15.5px; font-weight: 700; color: var(--ink); margin-top: 2px; }
.next-planned-when .muted { font-weight: 400; font-size: 13.5px; }
.next-planned-what {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  margin-top: 2px;
  font-family: var(--cond);
  font-size: 13px;
  color: var(--mut2);
}

/*
  What was actually recorded, beside what was planned. The comparison is the whole line: "8 km
  planned" is the intention and "7,2 km" is the day, and only the two together say anything.

  Quieter than the status word it follows - the athlete's eye should land on "FATTA" and then read
  the figures, not arbitrate between two things shouting equally.
*/
.planned-actual { display: inline-flex; flex-wrap: wrap; gap: 0 8px; margin-left: 8px; color: var(--mut2); }
.planned-percent { color: var(--ink-2); }

/*
  ---------- the week the coach proposes ----------

  A card that has to read as "not yours yet". Same neon family as everything else, but outlined in
  the accent rather than filled: it is a suggestion sitting on the page next to the athlete's own
  plan, and the two must not look like one list.
*/
.planned-propose { display: flex; flex-direction: column; gap: 6px; margin-top: 18px; }
.planned-propose-cost { font-size: 12.5px; }

.proposal { border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); }
.proposal-rationale { font-size: 14.5px; line-height: 1.5; margin: 0 0 4px; }

/*
  Seven day rows, one per day of the coming week.

  Rest is stated by the layout: an empty row IS a rest day, and no paragraph can claim otherwise.
  The empty rows are also what a session is dropped onto - a target you cannot see is not a target.
*/
.proposal-week { list-style: none; margin: 16px 0 0; padding: 0; }
.proposal-day {
  padding: 10px 0;
  border-bottom: 1px solid var(--line);
  border-radius: 10px;
}
.proposal-day:last-child { border-bottom: none; }
.proposal-day-head { display: flex; align-items: baseline; gap: 8px; }
/* The "rest" badge is in the markup on every row and shown only on the empty ones. CSS rather than
   Thymeleaf, so a row that becomes empty by dragging its session away says so immediately - the
   script toggles one class on the row and the badge, the dimming and the rest line all follow. */
.proposal-day-free {
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--mut2);
}
.proposal-day:not(.proposal-day-rest) .proposal-day-free { display: none; }
/* A day with nothing on it recedes: what the athlete reads is the training. */
.proposal-day-rest .proposal-day-head { opacity: .6; }

/* The day a dragged session would land on. Outlined rather than filled, so the row's own content
   stays readable underneath the mark. */
.proposal-day.is-target {
  outline: 2px dashed var(--accent);
  outline-offset: 2px;
  background: color-mix(in srgb, var(--accent) 7%, transparent);
}

/*
  A proposed session is a thing you move, not text you select.

  Reported from a phone: long-pressing the grip to drag started the browser's own text-selection
  gesture instead - selection handles across the card and "Copia | Cerca con Google" over the week.
  The drag worked underneath it, which made it worse: two gestures at once, one of them nobody asked
  for.
    - user-select: none on the row, not only on the grip. The browser expands a selection from the
      touch point into the nearest text, so making the grip unselectable was not enough.
    - -webkit-touch-callout: none is what suppresses the iOS callout menu specifically; it is not
      implied by user-select, and without it Safari still offers Copy.
  The notes and figures stop being selectable, which costs nothing: there is nothing on these rows
  anybody copies, and the session they belong to does not exist yet.
*/
.proposal-item {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
/*
  The session being dragged, lifted off the page and following the finger.

  pointer-events: none is the load-bearing line, and it is not obvious. The dragged element sits
  under the pointer by definition, so hit-testing to find the day being dropped onto returns the
  dragged session itself - and therefore the day it came FROM. Every drop landed back where it
  started until this was added.
*/
.proposal-item.is-dragging {
  position: relative;
  z-index: 5;
  opacity: .9;
  pointer-events: none;
  box-shadow: 0 6px 18px rgb(0 0 0 / .45);
}

/*
  The grip. touch-action: none on this and nothing else: without it the browser claims the gesture
  for scrolling and the drag never starts, and with it on the whole row the page could not be
  scrolled by touching a session. 44px tall because it is a tap target on a phone.
*/
.proposal-grip {
  touch-action: none;
  cursor: grab;
  align-self: center;
  min-width: 32px;
  min-height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--mut2);
  font-size: 18px;
  letter-spacing: -2px;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
/* The dots are drawn by the stylesheet rather than written in the markup: the content of a
   pseudo-element is not part of the document, so there is no text under the finger for a long press
   to latch onto in the first place. */
/* U+28FF, the six-dot braille cell: it reads as a grip, which is what the markup used to
   hold before the dots moved in here. */
.proposal-grip::before { content: "\28FF"; }
.proposal-grip:active { cursor: grabbing; }

/* While a drag is running, nothing anywhere is selectable. The row's own rule covers the session
   being moved; this covers the day rows it passes over, which is where a stray selection appeared
   after the first fix. Set and removed by app.js, so it lasts exactly as long as the drag. */
body.is-dragging-session,
body.is-dragging-session * {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

.proposal-move-by-hand { margin-top: 12px; }
.proposal-move-by-hand summary {
  font-family: var(--cond);
  font-size: 12.5px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--mut2);
  cursor: pointer;
}

/* The whole row is the tick target. A 20px checkbox on a phone is a miss waiting to happen, and the
   athlete is being asked to make one decision per row - the row is the decision. */
.proposal-pick {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin: 0;
  padding: 12px 0;
  min-height: 44px;
  cursor: pointer;
  flex: 1;
}
.proposal-pick input { width: 20px; height: 20px; margin-top: 2px; flex: 0 0 auto; accent-color: var(--accent-text); }
/* An unticked row recedes, so what will actually be added is readable at a glance. */
.proposal-pick:not(:has(input:checked)) .proposal-body { opacity: .5; }
.proposal-body { display: flex; flex-direction: column; gap: 2px; }

.proposal-actions { margin-top: 16px; }
.proposal-discard { margin-top: 10px; }

/* The rest days, computed from the proposal rather than claimed in its paragraph. Quiet when there
   are some; in the warning ink when there are none, because that is a contradiction the athlete
   should see without counting rows. */
.proposal-rest {
  font-family: var(--cond);
  font-size: 12.5px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--mut2);
  margin: 14px 0 0;
}
.proposal-rest-none { color: var(--danger-ink); }

/*
  ---------- the scales ----------

  The reading is large because it is the thing being looked up, and the direction is beside it in
  words rather than as a signed number: "-1,2 kg" reads as a quantity with a typo in front of it.

  The chart reuses .day-bars from the dashboard - one drawing of "a bar per day" in this application,
  not two. What differs is only that there can be forty columns instead of seven, so they are allowed
  to be narrow and the labels are turned to fit.
*/
/* Il campo e la sua unita' su una riga, il pulsante sotto e largo quanto la carta.

   Chiesto cosi': "il bottone peso deve essere sotto la input non di lato". Ed e' giusto anche per
   come si usa: si scrive un numero con la tastiera aperta, e un pulsante accanto al campo finisce
   stretto - quello che restava dopo il numero e il "kg" - proprio mentre e' l'unica cosa da premere
   dopo. Sotto e a tutta larghezza e' un bersaglio che non si sbaglia col pollice. */
.weight-row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 8px 10px;
}
.weight-row input {
  min-width: 0;
  min-height: 44px;
}
.weight-row .btn {
  grid-column: 1 / -1;
  width: 100%;
  margin-top: 4px;
}
.weight-unit { font-family: var(--cond); font-size: 13px; color: var(--mut); }
.weight-note { margin-top: 8px; font-size: 12px; line-height: 1.5; }

.weight-now { display: flex; align-items: baseline; gap: 6px; margin: 0 0 10px; }
.weight-now-value { font-size: 40px; font-weight: 700; color: var(--accent-text); line-height: 1; }
.weight-now-unit { font-family: var(--cond); font-size: 14px; color: var(--mut); }
.weight-now-day { margin-left: auto; font-family: var(--cond); font-size: 12px; color: var(--mut); }

.weight-changes { list-style: none; margin: 0 0 10px; padding: 0; }
.weight-changes li {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 6px 0;
  border-bottom: 1px solid var(--line-soft);
  font-size: 14px;
}
.weight-changes li:last-child { border-bottom: none; }
.weight-change-span {
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1px;
  color: var(--mut);
  min-width: 96px;
}
.weight-change-word { color: var(--txt); }
.weight-change-size { margin-left: auto; font-weight: 600; color: var(--accent-text); }
.weight-target { margin: 0 0 12px; font-size: 13px; color: var(--mut); }
.weight-empty { margin-top: 12px; }

/*
  Il grafico delle pesate: punti uniti da una linea, su un asse fisso da 0 a 100 kg.

  Le coordinate stanno negli attributi dell'SVG - le calcola WeightChart - e qui restano soltanto i
  colori e gli spessori, che e' l'unica parte che deve cambiare col tema. La larghezza e' quella
  della carta e l'altezza la decide il viewBox, quindi il disegno si adatta al telefono senza che
  nessun numero sia scritto due volte.
*/
.weight-chart { display: block; width: 100%; height: auto; margin: 4px 0 0; overflow: visible; }
/* Le righe dell'asse stanno dietro: sono un riferimento, non un dato. */
.weight-grid { stroke: var(--line-soft); stroke-width: 1; }
.weight-grid-label {
  fill: var(--mut2);
  font-family: var(--cond);
  font-size: 10px;
  letter-spacing: .5px;
}
/* La linea non ha riempimento: e' una spezzata aperta, e senza questo il browser la chiude e la
   colora di nero. */
.weight-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
}
.weight-dot { fill: var(--accent); stroke: var(--bg); stroke-width: 1.5; }
/*
  L'obiettivo, in verde e piu' sottile della linea del peso.

  Verde perche' e' l'unico colore di questa applicazione che vuol dire "arrivato" e non appartiene a
  nessuna disciplina, e perche' il colore e' l'unica cosa che la distingue: non puo' essere
  tratteggiata, dato che il tratteggio serve gia' a disegnarla da sinistra a destra. Piu' sottile e
  un po' trasparente per la stessa ragione - e' una soglia, non una seconda serie di dati.

  Il colore non passa dall'accento: l'accento cambia con il tema scelto dall'atleta, e un obiettivo
  che diventa rosa o arancione a seconda della giornata smette di essere riconoscibile a colpo
  d'occhio.
*/
.weight-target-line {
  stroke: var(--ok);
  stroke-width: 1.5;
  opacity: .85;
}

/*
  ---------- la linea corre da sinistra a destra e accende i punti che incontra ----------

  "Crea animazione della linea del grafico che scorre da sx verso dx toccando i vari punti, in 2 sec."

  Il disegno vero e' il tratteggio: la linea porta un trattino lungo quanto se stessa, e spostarlo
  da un capo all'altro la scopre da un'estremita'. Quello che rende la cosa possibile senza sapere
  quanto e' lunga - la lunghezza dipende da quante pesate ci sono e da quanto salgono e scendono - e'
  pathLength="100" nel markup: il percorso dichiara di essere lungo cento, e cento e' un numero che
  un fotogramma CSS puo' scrivere per chiunque.

  Il fotogramma ha solo "from". Il valore d'arrivo e' quello scritto nell'attributo, cioe' zero, cioe'
  la linea intera: e' lo stesso espediente dell'anello della condizione fisica, ed e' anche cio' che
  tiene il disegno giusto da fermo. Chi ha chiesto meno movimento, chi ha gli script spenti e chi
  arriva a pagina gia' scorsa vede la linea completa, non una linea a meta'.

  Lineare e non ammorbidita: e' una cosa che avanza a velocita' costante lungo un asse del tempo,
  non un oggetto che parte e si ferma. Un'accelerazione qui farebbe sembrare che i giorni al centro
  siano passati piu' in fretta.
*/
@media (prefers-reduced-motion: no-preference) {
  /* La riga dell'obiettivo corre con quella del peso: "che corre da sx a dx come la linea del
     peso". Stessa durata e stessa andatura - sono la stessa cosa vista due volte, dove sei e dove
     volevi essere, e due andature diverse le farebbero leggere come due grafici sovrapposti. */
  .weight-chart.is-visible .weight-line,
  .weight-chart.is-visible .weight-target-line { animation: weight-draw 2s linear; }
}
@keyframes weight-draw { from { stroke-dashoffset: 100; } }

/*
  E i punti si accendono quando la linea li raggiunge.

  Il ritardo e' una classe, non uno stile in linea, perche' la nostra Content-Security-Policy non
  ammette il secondo: ventuno regole, una ogni cinque per cento dei due secondi, che e' un decimo di
  secondo - piu' fine di cosi' non lo vede nessuno. E' lo stesso espediente delle altezze "line-N"
  che tutti gli altri grafici usano qui dentro, per la stessa ragione.

  Da fermi i punti sono opachi, come la linea: l'animazione li porta da invisibili a visibili, quindi
  senza animazione ci sono e basta.
*/
@media (prefers-reduced-motion: no-preference) {
  .weight-chart.is-visible .weight-dot { animation: weight-dot-in .25s ease-out backwards; }
  .weight-chart.is-visible .lights-0 { animation-delay: 0s; }
  .weight-chart.is-visible .lights-5 { animation-delay: .1s; }
  .weight-chart.is-visible .lights-10 { animation-delay: .2s; }
  .weight-chart.is-visible .lights-15 { animation-delay: .3s; }
  .weight-chart.is-visible .lights-20 { animation-delay: .4s; }
  .weight-chart.is-visible .lights-25 { animation-delay: .5s; }
  .weight-chart.is-visible .lights-30 { animation-delay: .6s; }
  .weight-chart.is-visible .lights-35 { animation-delay: .7s; }
  .weight-chart.is-visible .lights-40 { animation-delay: .8s; }
  .weight-chart.is-visible .lights-45 { animation-delay: .9s; }
  .weight-chart.is-visible .lights-50 { animation-delay: 1s; }
  .weight-chart.is-visible .lights-55 { animation-delay: 1.1s; }
  .weight-chart.is-visible .lights-60 { animation-delay: 1.2s; }
  .weight-chart.is-visible .lights-65 { animation-delay: 1.3s; }
  .weight-chart.is-visible .lights-70 { animation-delay: 1.4s; }
  .weight-chart.is-visible .lights-75 { animation-delay: 1.5s; }
  .weight-chart.is-visible .lights-80 { animation-delay: 1.6s; }
  .weight-chart.is-visible .lights-85 { animation-delay: 1.7s; }
  .weight-chart.is-visible .lights-90 { animation-delay: 1.8s; }
  .weight-chart.is-visible .lights-95 { animation-delay: 1.9s; }
  .weight-chart.is-visible .lights-100 { animation-delay: 2s; }
}
/* Un soffio piu' grande nell'istante in cui la linea lo tocca, e poi la sua misura: e' cio' che fa
   leggere il punto come acceso dalla linea invece che comparso li' per conto suo. */
@keyframes weight-dot-in {
  from { opacity: 0; r: 1; }
  60% { opacity: 1; r: 5; }
}
.weight-span {
  display: flex;
  justify-content: space-between;
  margin: 4px 0 0;
  font-family: var(--cond);
  font-size: 12px;
  color: var(--mut);
}

/*
  ---------- the written eating plan ----------

  Read in a kitchen: the food down the left, how much of it across on the right, and nothing between
  them competing for attention. The meal heading carries the hour and what the meal comes to, because
  those are the two things somebody checks before deciding whether they have time to cook it.
*/
.diet-lead { margin: 0 0 12px; font-size: 13px; line-height: 1.5; }
.diet-disclaimer {
  margin: 14px 0 0;
  padding: 10px 12px;
  border-left: 2px solid var(--tiffany);
  border-radius: 0 8px 8px 0;
  background: var(--field);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--mut);
}
.diet-totals {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin: 0 0 14px;
  padding: 0;
  list-style: none;
  font-size: 13px;
  color: var(--mut);
}
.diet-totals li > span:first-child { color: var(--accent-text); font-weight: 600; }

/*
  ---------- what to eat today ----------

  One line per meal above the plan itself: the name and the hour on the first line, the dishes on the
  second. The plan underneath carries the grammages in a proper list, so this is deliberately the
  cheaper layout - it answers "what am I eating at one o'clock", and the card below answers "how
  much".

  The next meal is the only thing marked, in the accent, because that is the one line somebody opens
  this page to read. Marking every meal would mark nothing.
*/
.diet-today-list { list-style: none; margin: 0; padding: 0; }
.diet-today-meal { padding: 10px 0; border-top: 1px solid var(--line-soft); }
.diet-today-meal:first-child { border-top: none; }
.diet-today-when { display: flex; align-items: baseline; gap: 8px; }
.diet-today-name { font-size: 15px; font-weight: 600; }
.diet-today-at { font-family: var(--cond); font-size: 12px; color: var(--mut); }
.diet-today-dishes {
  display: block;
  margin-top: 2px;
  font-size: 13px;
  line-height: 1.45;
  color: var(--ink-2);
}
.diet-today-now {
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--accent-text);
}
.diet-today-meal.is-next .diet-today-name { color: var(--accent-text); }

.diet-meal { padding: 12px 0; border-top: 1px solid var(--line-soft); }
.diet-meal:first-of-type { border-top: none; }
.diet-meal-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 6px; }
.diet-meal-name { font-size: 16px; font-weight: 600; }
.diet-meal-at { font-family: var(--cond); font-size: 12px; color: var(--mut); }
.diet-meal-kcal {
  margin-left: auto;
  font-family: var(--cond);
  font-size: 12px;
  color: var(--accent-text);
}
.diet-dishes { list-style: none; margin: 0; padding: 0; }
.diet-dishes li {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 5px 0;
  font-size: 14px;
  line-height: 1.45;
}
.diet-food { flex: 1 1 auto; min-width: 0; }
.diet-amount {
  flex: 0 0 auto;
  font-family: var(--cond);
  font-size: 13px;
  color: var(--accent-text);
  white-space: nowrap;
}
/* The swap, folded, with the chevron every other disclosure here uses. */
.diet-alt > summary {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  cursor: pointer;
  list-style: none;
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--mut);
}
.diet-alt > summary::-webkit-details-marker { display: none; }
/* -135 e non 90: la freccina di base e' gia' ruotata di 45 - e' un quadrato con due lati -
   quindi 90 non la gira, la riscrive, e da aperta veniva fuori una "L". Le altre pieghe
   usano questo valore da sempre; queste tre no, e si vedeva.
   diet-alt: */
.diet-alt[open] > summary .session-detail-mark { transform: rotate(-135deg) translateY(3px); }
.diet-alt p { margin: 0 0 6px; font-size: 13.5px; line-height: 1.5; color: var(--mut); }
.diet-notes {
  margin: 14px 0 0;
  padding-top: 12px;
  border-top: 1px solid var(--line-soft);
  font-size: 13px;
  line-height: 1.55;
  color: var(--mut);
}
/* Nessun margine proprio: e' una sezione della pagina e segue il passo delle altre. */
.diet-empty { font-size: 13px; }
/*
  ---------- the week, as seven rows ----------

  A row per day whether anything is on it or not: that is what makes a free day visible, and it is
  the whole reason this exists beside the list below. Today's row carries an edge in the accent, so
  it is found without reading seven dates.

  The grip is the only thing with touch-action: none. On the whole row the page could not be
  scrolled by touching a session, which is a worse problem than a drag that needs aiming.
*/
/* Ventidue e non dodici: lunedi' era troppo attaccato alla frase sopra. La sua riga non e' un
   paragrafo che segue un altro paragrafo, e' l'inizio di una lista - e una lista che comincia alla
   stessa distanza a cui vanno a capo le righe della frase sopra sembra l'ultima riga della frase. */
.agenda-hint { margin: 0 0 22px; font-size: 12px; line-height: 1.5; }
.agenda-week { list-style: none; margin: 0; padding: 0; }
.agenda-day {
  padding: 8px 0 8px 10px;
  border-left: 2px solid transparent;
  border-top: 1px solid var(--line-soft);
}
.agenda-day:first-child { border-top: none; }
.agenda-day-today { border-left-color: var(--accent); }
.agenda-day.is-target { background: var(--field); border-left-color: var(--tiffany); }

/* The days of this week that have already gone.
   Drawn, because they are what the week amounts to - a Saturday grid that started at today hid the
   four sessions that made the week - and drawn quieter, because they are a record rather than
   somewhere to plan. Faded rather than removed: nothing here is hidden from anybody, it is just not
   the part being decided. The drag script reads data-past and refuses them as drop targets, so the
   dimming and the behaviour say the same thing. */
.agenda-day-past { opacity: .55; }
.agenda-day-past .agenda-grip { display: none; }

/*
  Il titolo che apre il calendario e il modulo sotto: sono la stessa cosa in due pezzi, e senza una
  riga che li nomini il calendario sembrava un riepilogo da guardare invece che il modo di scegliere
  il giorno. Della stessa misura del titolo del mese, ma nell'inchiostro della pagina: quello dentro
  e' un'etichetta, questo e' un titolo.
*/
.agenda-manual-h {
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--line-soft);
  font-size: 16.5px;
  font-weight: 700;
  color: var(--ink);
}

/*
  ---------- il mese, come calendario ----------

  Sotto le sette righe della settimana, al posto delle sette della settimana prossima. Quelle
  comparivano solo il sabato e la domenica ed erano sette giorni scelti dal momento in cui uno
  guardava; questo mostra il mese intero a chiunque, in qualunque giorno.

  Sette colonne uguali, quindi grid e non flex: le colonne devono restare incolonnate riga dopo
  riga, ed e' l'unica cosa che un flex non fa da solo.
*/
.agenda-month { margin-top: 14px; }
/* Il modulo viene subito dopo il calendario, ed erano attaccati: misurati, zero pixel fra l'ultima
   riga di caselle e la prima etichetta. Lo stesso passo delle altre sezioni della pagina. */
.agenda-month + .planned-form { margin-top: 18px; }
.agenda-month-h {
  margin-bottom: 12px;
  font-family: var(--cond);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--accent-text);
}
.agenda-month-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 3px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.agenda-month-weekday {
  padding-bottom: 4px;
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1px;
  text-align: center;
  color: var(--mut2);
}
/* La casella e' quadrata e il collegamento la riempie tutta: la zona da premere e' il giorno
   intero, non il numero dentro. */
.agenda-month-cell { aspect-ratio: 1; }
.agenda-month-cell > * {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  border-radius: 10px;
  background: var(--field);
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-2);
  text-decoration: none;
}
.agenda-month-cell a:hover { background: var(--track); }
/* I giorni degli altri due mesi restano nel calendario perche' completano le settimane, ma non
   sono questo mese e non devono pesare come i suoi. */
.agenda-month-out > * { background: none; color: var(--mut2); }
.agenda-month-past > * { opacity: .5; }
/* Oggi: un anello, non un riempimento. Il riempimento e' gia' il segno di "qui c'e' qualcosa". */
.agenda-month-today > * { box-shadow: inset 0 0 0 1.5px var(--accent); color: var(--ink); }
/* Un giorno con qualcosa dentro, e quante cose. Il numero sta in basso a destra e non al centro:
   al centro c'e' gia' il giorno, e due cifre sovrapposte non si leggono ne' l'una ne' l'altra. */
.agenda-month-busy > * { background: var(--accent-soft); color: var(--ink); }
.agenda-month-dot {
  position: absolute;
  right: 3px;
  bottom: 2px;
  min-width: 13px;
  height: 13px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  font-family: var(--cond);
  font-size: 11px;
  font-weight: 600;
  line-height: 13px;
  text-align: center;
}
.agenda-day-head { display: flex; align-items: baseline; gap: 8px; }
.agenda-day-head .planned-day { font-size: 14px; }
.agenda-day-head .planned-date { font-family: var(--cond); font-size: 12px; color: var(--mut); }
/* Only on an empty row, and always rendered: a row emptied by dragging its session away has to be
   able to say "riposo" without the server drawing the page again. */
.agenda-day-rest {
  display: none;
  margin-left: auto;
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--mut2);
}
.agenda-day-free .agenda-day-rest { display: inline; }

.agenda-item {
  display: flex;
  /* Va a capo perche' il corpo di "gestisci", aperto, si prende tutta la riga: vedi
     .agenda-manage-body qui sotto. Chiuso non c'e' niente da mandare a capo e la riga resta una
     riga. */
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 6px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--field);
}
.agenda-item.is-dragging {
  border-color: var(--tiffany);
  box-shadow: 0 8px 24px rgba(0, 0, 0, .45);
  position: relative;
  z-index: 2;
  /*
    pointer-events: none while it is being dragged, and it is load-bearing rather than cosmetic.
    The row follows the finger, so it sits under the pointer - and document.elementFromPoint then
    returns the row itself, whose closest(".agenda-day") is the day it started in. Every drop read
    as "back where it came from" and moved nothing. Same line, same reason, as the proposal above.
  */
  pointer-events: none;
}
.agenda-item-done { opacity: .6; }
/* In fondo a destra, e i puntini li disegna il foglio di stile: il contenuto di uno
   pseudo-elemento non fa parte del documento, quindi sotto il dito non c'e' testo che una pressione
   lunga possa selezionare. E' lo stesso rimedio - e lo stesso disegno - di .proposal-grip. */
.agenda-grip {
  flex: 0 0 auto;
  order: 9;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  min-height: 44px;
  color: var(--mut2);
  font-size: 18px;
  letter-spacing: -2px;
  cursor: grab;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
.agenda-grip::before { content: "\28FF"; }
.agenda-grip:active { cursor: grabbing; }
.agenda-item-main { flex: 1 1 auto; min-width: 0; display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: baseline; }
.agenda-sport { font-size: 14px; }
.agenda-detail { font-family: var(--cond); font-size: 12px; color: var(--mut); }
.agenda-move { flex: 0 0 auto; }
/*
  "Gestisci" resta nella riga, il suo contenuto scende sotto largo quanto la carta.

  Le due meta' sono due elementi, e devono esserlo: un <details> e' una scatola sola, quindi da
  aperto o restava stretto nella colonna accanto al nome - con tendina e bottoni a capo su ogni
  parola - o scendeva sotto portandosi via anche il titolo. "Deve sempre rimanere dove indica la
  freccia, non andare sotto."

  L'etichetta e' l'ultimo elemento della riga e ci si tiene con margin-left: auto; il corpo e' un
  fratello con base 100%, quindi va a capo e prende tutta la larghezza. Il binario dell'accento sta
  sul corpo, dove serve, invece di correre in mezzo alla riga.

  La casella e' fuori schermo e non hidden: hidden la toglierebbe dall'ordine di tabulazione, e
  questo comando deve restare raggiungibile da tastiera.
*/
.agenda-manage-toggle { position: absolute; left: -9999px; }
.agenda-manage {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-height: 44px;
  margin-left: auto;
  padding: 0 4px;
  cursor: pointer;
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--mut);
}
.agenda-manage:hover { color: var(--accent-text); }
.agenda-manage-toggle:focus-visible + .agenda-manage {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.agenda-manage-body { display: none; }
.agenda-manage-toggle:checked ~ .agenda-manage-body {
  display: block;
  flex: 1 0 100%;
  margin-top: 6px;
  padding-left: 14px;
  border-left: 2px solid var(--accent-line);
}
.agenda-manage-toggle:checked ~ .agenda-manage .session-detail-mark {
  transform: rotate(-135deg) translateY(3px);
}
.agenda-manage-body select { width: 100%; min-height: 44px; margin: 4px 0 6px; }

/* "e altri 4": the rest of the day's meals, and the count is the reason to press it. */
.meal-more > summary {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  cursor: pointer;
  list-style: none;
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--mut);
}
.meal-more > summary::-webkit-details-marker { display: none; }
.meal-more > summary:hover { color: var(--accent-text); }
/* -135 e non 90: la freccina di base e' gia' ruotata di 45 - e' un quadrato con due lati -
   quindi 90 non la gira, la riscrive, e da aperta veniva fuori una "L". Le altre pieghe
   usano questo valore da sempre; queste tre no, e si vedeva.
   meal-more: */
.meal-more[open] > summary .session-detail-mark { transform: rotate(-135deg) translateY(3px); }

/* The scanner's own note, and the badge saying who wrote the plan on screen. */
.diet-scan-note { margin: 12px 0 0; font-size: 12.5px; line-height: 1.5; }
.diet-source { margin-left: auto; }

/* ---------- the confirmation dialogue ----------

   One dialogue for every deletion in the application (layout.html, app.js). The browser's own
   confirm() said "koraei-production.up.railway.app dice" above the question and offered its own two
   words underneath, which reads as a fault rather than as a question. This is the same question in
   the application's type and colours, with "Annulla" and "Conferma" from the message bundle.

   ::backdrop is the platform's, so the page behind it dims without a div pretending to be one. */
.confirm-dialog {
  /* Centred by hand. A modal <dialog> is laid out by the browser as inset:0 with margin:auto, which
     centres it - unless something in the page's own reset has given dialogs a top or a position, and
     then it lands in a corner. It did: it opened against the top-left, which reads as a panel that
     failed to load rather than as a question. Four lines that cannot be argued with. */
  position: fixed;
  inset: 0;
  margin: auto;
  height: fit-content;
  width: min(340px, calc(100vw - 40px));
  padding: 0;
  border: 1px solid var(--accent-line);
  border-radius: 18px;
  background: var(--panel);
  color: var(--ink);
  box-shadow: 0 18px 48px rgba(0, 0, 0, .45);
}
.confirm-dialog::backdrop { background: rgba(0, 0, 0, .55); }

.confirm-form { padding: 22px 20px 18px; }

.confirm-question {
  margin: 0 0 20px;
  font-size: 15px;
  line-height: 1.5;
  color: var(--ink);
}

/* Side by side while they fit, stacked when the wording is long: an Italian confirmation runs to
   two words where the English runs to one, and a button whose label wraps is a button that looks
   broken. */
.confirm-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
.confirm-actions .btn {
  flex: 1 1 130px;
  margin: 0;
  width: auto;
}

/* ---------- the overall reading's way into the chat ----------

   Asked for by Davide, for the space directly under the coach's comment at the top of the page: a
   line to type a question into that lands in the chat already sent.

   It was drawn to match the coach card's own ask line, which sat one screen below it; that card is
   gone now and this is the only ask line left on the home, so these rules are the whole of it. */
.box-chat-ask {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 12px 0 0;
}

/*
  Aperto il consiglio, la riga per rispondere entra nel riquadro.

  "Una volta espanso voglio che il box scrittura venga inglobato nel box del commento subito sotto,
  mentre da ridotto deve rimanere come visibile subito sotto, fuori."

  Ha ragione ed e' la stessa cosa detta due volte. Da chiuso il consiglio e' un riassunto e la riga
  sotto e' un'altra cosa - si chiede quello che si vuole. Da aperto il consiglio finisce con una
  domanda, e la riga e' il posto dove si risponde a QUELLA: un campo staccato sembra un secondo
  comando invece della seconda meta' della stessa frase.

  Fatto senza una riga di JavaScript e senza toccare l'ordine nel documento: la riga sta gia' subito
  sotto al riquadro, e quando la piega e' aperta - :has(), lo stesso meccanismo con cui la vetrina
  cambia colore - prende i bordi che al riquadro qui sopra tolgo. Bordo inferiore via da uno,
  superiore via dall'altro, e i due si leggono come una scatola sola.

  Dove :has() non c'e', la regola non si applica affatto e resta tutto com'e' oggi: il peggio che
  puo' succedere e' la pagina di ieri.
*/
.box-comment:has(.advice-more[open]) .ai-feedback { margin-bottom: 0; }
.box-comment:has(.advice-more[open]) .ai-feedback-box {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
  border-bottom: none;
  padding-bottom: 10px;
}
.box-comment:has(.advice-more[open]) .box-chat-ask {
  margin: 0;
  padding: 0 14px 13px;
  border: 1px solid color-mix(in srgb, var(--accent) 26%, transparent);
  border-top: none;
  border-radius: 0 0 14px 14px;
  background: var(--accent-soft);
}
.box-chat-ask-input {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  border-radius: 999px;
  padding: 11px 15px;
  font-size: 15px;
}
.box-chat-ask-input::placeholder { color: var(--mut2); opacity: 1; }
.box-chat-ask-send {
  /* 44px, like every other tap target in this application. */
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--accent-line);
  background: var(--accent-soft);
  /* The arrow is text, so it takes the text token rather than the raw accent: over its own pale
     fill the raw accent measures below 3:1 on the light theme. */
  color: var(--accent-text);
  font-size: 18px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.box-chat-ask-send:hover { background: var(--accent); color: var(--on-accent); }

/* ---------- making every fold look like something you can press ----------

   Reported by Davide: "rendi tutte le interazioni di espansione e compressione più evidenti,
   rischiamo non sia comprensibile a primo impatto". He is right, and the cause is that these
   controls were designed to be quiet: 12px condensed grey, the same treatment as a caption, with a
   chevron the size of a comma beside it. Quiet was the goal - a page of five open forms was the
   problem they solved - but quiet was taken far enough that they stopped reading as controls.

   Rather than invent a look, this promotes the one already in the stylesheet: .macro-edit's summary,
   a bordered pill in the accent line with accent text. It is the one disclosure on the nutrition
   card nobody has ever had to be told about.

   Two changes, and between them they cover every fold in the application:

   1. The shared chevron becomes a round bordered badge. That mark is used by the session details,
      the card folds, the plan entries, the meal list, the diet alternatives, the agenda's move
      control and the goals - six families from one rule.
   2. The folds that draw their own caret get the pill.

   Nothing gets bigger than 44px and nothing moves, so the page keeps its shape; what changes is that
   the control now has an edge. */

/* 1. The chevron is a badge now, and the change is made in its ONE rule above rather than in a
      second one down here. A class defined twice is a class whose behaviour depends on which
      declaration a reader happens to find first, and StylesheetClashTest refuses it - correctly:
      that is exactly the bug this stylesheet had with the nutrition pill.

/* 2. Le pieghe di solo testo: la stessa cosa delle pieghe delle carte, non una seconda.

      "Gli espandi devono avere tutti questo stile, no border ecc, ovviamente del colore coerente
      al tema." Erano pillole: bordo, fondo, un'altezza propria. Adesso sono quello che e' gia' il
      controllo delle carte - etichetta condensata in maiuscolo nel colore dell'accento, freccina
      che gira, niente altro - allineate a destra e con la stessa area da toccare.

      Una regola sola per tutte e cinque le famiglie. Un controllo che compare in cinque punti e ha
      cinque regole e' un controllo che diverge al primo che se ne tocca una. */
/* "Espandi" da chiusa, "Riduci" da aperta: la stessa coppia di .meal-more, perche' e' lo stesso
   problema - un comando deve dire cosa succede a premerlo, non in che stato si trova. */
.card-fold .card-fold-close,
.card-fold[open] .card-fold-open { display: none; }
.card-fold[open] .card-fold-close { display: inline; }

.card-fold > summary,
.advice-more > summary,
.last-session-more > summary,
.nutrition-history > summary,
.memory-edit > summary,
.session-bars > summary {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  width: auto;
  min-height: 44px;
  padding: 0;
  border: none;
  background: none;
  border-radius: 0;
  cursor: pointer;
  list-style: none;
  font-family: var(--cond);
  font-size: 12.5px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  /* Il colore dell'accento, quindi cambia col tema come tutto il resto. */
  color: var(--accent-text);
  text-align: right;
}
.card-fold > summary::-webkit-details-marker,
.advice-more > summary::-webkit-details-marker,
.last-session-more > summary::-webkit-details-marker,
.nutrition-history > summary::-webkit-details-marker,
.memory-edit > summary::-webkit-details-marker,
.session-bars > summary::-webkit-details-marker { display: none; }
.card-fold > summary:hover,
.advice-more > summary:hover,
.last-session-more > summary:hover,
.nutrition-history > summary:hover,
.memory-edit > summary:hover,
.session-bars > summary:hover { color: var(--accent); }
.card-fold > summary:focus-visible,
.advice-more > summary:focus-visible,
.last-session-more > summary:focus-visible,
.nutrition-history > summary:focus-visible,
.memory-edit > summary:focus-visible,
.session-bars > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Le quattro che non avevano una freccina se la prendono qui: il segno e' del controllo, non della
   famiglia. .session-detail-mark, dove c'e' nel markup, e' esattamente lo stesso disegno.

   .advice-more e' uscita da qui per un giorno, con una freccia grossa e disegnata tutta sua che
   doveva dire "sotto ce n'e' dell'altro". Detto guardandola: "sta freccia e' orribile". E' rientrata
   e la v e' tornata quella di tutte le altre - la parola accanto, "leggi tutto", basta da sola a
   dire cosa succede a premerla. */
.advice-more > summary::after,
.last-session-more > summary::after,
.nutrition-history > summary::after,
.memory-edit > summary::after,
.session-bars > summary::after {
  content: "";
  flex: 0 0 auto;
  width: 9px;
  height: 9px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translateY(-3px);
  transition: transform .15s ease;
}
.advice-more[open] > summary::after,
.last-session-more[open] > summary::after,
.nutrition-history[open] > summary::after,
.memory-edit[open] > summary::after,
.session-bars[open] > summary::after { transform: rotate(-135deg) translateY(3px); }
@media (prefers-reduced-motion: reduce) {
  .advice-more > summary::after, .last-session-more > summary::after,
  .nutrition-history > summary::after, .memory-edit > summary::after { transition: none; }
}

/*
  Il <details> resta un blocco normale, e il riassunto si allinea da solo.

  C'era qui una colonna flex allineata in fondo, messa per spingere l'"espandi" a destra. Faceva il
  suo lavoro e ne rompeva un altro: in una colonna allineata cosi' ogni figlio si restringe sul
  proprio contenuto, quindi l'elenco dei sette giorni e il blocco della seduta precedente uscivano
  larghi meta' carta e spostati a destra. Un align-self: stretch avrebbe dovuto rimediare, e in
  effetti risultava applicato - ma la larghezza usata restava quella del contenuto, e a quel punto
  si stava rattoppando un meccanismo che non serviva.

  Non serve perche' il riassunto e' gia' un blocco largo quanto la carta con
  justify-content: flex-end dentro: sono le sue parole ad andare a destra, non la sua scatola. E'
  esattamente come funziona .card-fold, che infatti non ha mai avuto questo problema. Tolto il
  flex, il contenuto aperto torna a occupare tutta la carta da solo.
*/

/*
  La "cosa alleni?" della pagina obiettivi: una carta intera piegata, che pero' porta con se'
  l'intestazione della sezione. Per questo tiene la misura di un titolo invece dei 13px qui sopra.

  E per lo stesso motivo non segue le altre pieghe nel resto: la regola comune manda tutto a destra
  e mette il conteggio prima dell'etichetta, che va bene per un controllo che dice solo "espandi" e
  non va bene qui, dove l'etichetta e' il titolo. Veniva fuori "TOCCA PER MODIFICARE ^ Cosa
  alleni?": la riga letta al contrario, ammucchiata a destra e addosso al paragrafo sotto.

  Qui l'ordine e' quello di una intestazione - titolo a sinistra, suggerimento e freccia all'altro
  capo - ed e' dichiarato invece che lasciato al markup, perche' nel markup la freccia viene per
  prima, dove serve a tutte le altre pieghe.
*/
.discipline-card > summary {
  justify-content: flex-start;
  text-align: left;
  font-size: 16.5px;
  font-family: var(--sans);
  letter-spacing: 0;
  text-transform: none;
  font-weight: 700;
  color: var(--ink);
}
.discipline-card > summary .session-detail-mark {
  order: 2;
}
.discipline-card > summary .card-fold-count {
  order: 1;
  margin-left: auto;
  font-family: var(--cond);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--accent-text);
}

/*
  L'intestazione dell'elenco delle discipline dentro la richiesta di una settimana. Non c'e' nella
  pagina obiettivi, dove l'elenco e' gia' il contenuto della sua carta; qui viene dopo tre domande,
  e senza una riga che lo separi le caselle sembravano una quarta domanda.
*/
/*
  ---------- chiedere una settimana: da chiusa la riga E' il bottone ----------

  Il riassunto porta le classi del bottone primario, quindi da chiuso quello che si vede e' il
  bottone rosa che stava in fondo alla pagina - largo, con il suo alone, impossibile da non vedere.
  "Il bottone deve rimanere cosi'": e' l'invito principale della pagina, e travestito da riga di
  intestazione non si vedeva piu'.

  Un <summary> porta un marcatore suo, che va tolto o compare un triangolino dentro la pillola.

  Da aperto smette di essere un bottone: fondo via, alone via, testo dell'inchiostro normale. Il
  rosa torna in fondo al modulo, dove sta quello che invia davvero - due bottoni rosa identici sullo
  stesso schermo, uno che apre e uno che spende un'interazione, sarebbero indistinguibili.
*/
.propose-card { margin-top: 18px; }
.propose-open { margin-top: 0; cursor: pointer; list-style: none; }
.propose-open::-webkit-details-marker { display: none; }
.propose-card[open] > .propose-open {
  justify-content: space-between;
  /* Sedici a sinistra e non quattro: da aperta la piega ha il binario dell'accento addosso, e il
     titolo ci si appoggiava contro - "la scritta e' troppo attaccata al bordo sinistro". Sedici e'
     esattamente il rientro del contenuto qui sotto (14 di spazio piu' i 2 del binario), quindi il
     titolo non si stacca e basta: si incolonna con cio' che apre. */
  padding-inline: 16px 4px;
  background: none;
  box-shadow: none;
  color: var(--ink);
}
/* La freccia guarda in giu' da chiusa e in su da aperta, come tutte le altre. currentColor perche'
   sul rosa deve prendere l'inchiostro del bottone: l'accento sull'accento sparisce. */
.propose-card[open] > .propose-open .session-detail-mark { transform: rotate(-135deg) translateY(3px); }

.propose-load-h {
  margin: 20px 0 10px;
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
}

/* ---------- the week, as seven columns ----------

   Drawn by Davide, and rebuilt on his second look: "è terribile... la spunta deve essere una sola
   non una per ogni attività". The first version stacked a mark per session, which on a column forty
   pixels wide is a list with no room to be one - and on a day whose gym session had been recorded
   twice it drew the same workout as two separate marks.

   One mark per day now, and the column is a tile rather than a stripe: the state colours the whole
   tile instead of only the badge inside it, so the week reads as a row of days at a glance and not
   as a row of symbols to decode.
*/
.week-strip {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
  list-style: none;
  margin: 14px 0 0;
  padding: 0;
}
.week-day {
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 10px 3px 8px;
  border-radius: 12px;
  border: 1px solid var(--line-soft);
  background: var(--field);
}

/* The mark. One box, always the same size, so the seven tiles stay on one baseline whether the day
   has a mark in it or not. */
.week-mark {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  line-height: 1;
  border: 1px solid var(--line);
  background: transparent;
  color: transparent;
}
.day-done .week-mark {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
.day-missed .week-mark {
  background: rgba(255, 92, 92, .18);
  border-color: var(--danger);
  color: var(--danger-ink);
}
/* Planned and still ahead: an outline, because nothing has become of it. A tick here would be the
   application claiming a session that has not happened. */
.day-ahead .week-mark { border-style: dashed; border-color: var(--accent-line); }
.day-rest .week-mark { border-color: var(--line-soft); }

/* The tile takes a tint of its own state, which is what makes the row readable without reading. */
.week-day.day-done { border-color: var(--accent-line); background: var(--accent-soft); }
.week-day.day-missed { border-color: rgba(255, 92, 92, .35); }
.week-day.day-rest { background: transparent; }

/* Today, marked the way the agenda and the day chart mark it. */
.week-day-today { box-shadow: inset 0 0 0 1px var(--accent); }
.week-day-today .week-weekday { color: var(--accent-text); }
/* Days already gone stay legible - they are the week's record - and stop competing with the days
   that can still be acted on. */
.week-day-past { opacity: .78; }

.week-sport {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: .2px;
  color: var(--ink);
  text-align: center;
  /* One word per line and never a broken one: "Camminata" is longer than its column. */
  overflow-wrap: anywhere;
}
.day-rest .week-sport { color: var(--mut2); }
/* Ogni disciplina e' una riga della colonna, e i chilometri previsti stanno sotto il suo nome:
   piu' piccoli e in grigio, perche' la parola dice cosa si fa e la cifra quanto. La riga rossa del
   saltato li prende insieme, ed e' giusto - a non essere successa e' la seduta intera. */
.week-sport > span { display: block; }
.week-km { font-size: 10.5px; letter-spacing: 0; color: var(--mut); }
/*
  La disciplina programmata e non fatta, barrata di rosso.

  Il giorno ha un segno solo e resta cosi' - una colonna larga quaranta pixel non ne regge due. Ma
  con tre nomi sotto una spunta sola, la spunta finiva per dire "fatte tutte e tre": "mi spunta la
  corsa anche come fatta, quando non l'ho fatta". La riga risponde per ogni nome senza aggiungere un
  segno alla colonna.

  Riga rossa e parola grigia: il rosso e' sulla riga, non sulla parola. Una parola rossa E barrata
  dice due volte la stessa cosa, e questa e' una colonna che si guarda, non che si legge.
*/
.week-sport-missed {
  text-decoration: line-through;
  text-decoration-color: var(--danger);
  text-decoration-thickness: 2px;
  color: var(--mut2);
}
.week-weekday {
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--mut);
}

/* The sentence under the strip. It carries the verdict's own name as a class so an encouraging
   reading and a warning one are not the same colour - the card used to have one register, and one
   register is what made a good week produce a complaint. */
.week-verdict {
  margin: 14px 0 0;
  padding: 11px 13px;
  border-radius: 12px;
  border: 1px solid var(--line-soft);
  border-left: 3px solid var(--accent);
  background: var(--field);
  font-size: 14px;
  line-height: 1.45;
  color: var(--ink);
}
.week-verdict.verdict-nothing_planned { border-left-color: var(--line); color: var(--mut); }
/* Le discipline che mancano, una in fila all'altra dentro la stessa frase. La virgola la mette il
   foglio di stile perche' il numero di pezzi lo decide l'atleta: chi ne ha una sola non deve
   leggersi una virgola in fondo. */
.week-left-gap + .week-left-gap::before { content: ", "; }

/* ---------- the sync line at the foot of the dashboard ----------

   What is left of a card that used to be a whole screen: last sync, "sync now", "disconnect",
   "configure in Strava". Connecting an account is a setup task done once and then forgotten, and it
   was taking a screen of the page an athlete opens to find out what to do today. It lives on
   /connections now; this is a line that says whether it is on and gets you there. */
.sync-line {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  padding: 0 14px;
  border-radius: 999px;
  border: 1px solid var(--line-soft);
  font-family: var(--cond);
  font-size: 12.5px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--mut);
  text-decoration: none;
}
.sync-line:hover { border-color: var(--accent-line); color: var(--accent-text); text-decoration: none; }
.sync-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
.sync-on { background: var(--accent); box-shadow: 0 0 8px var(--accent); }
.sync-off { background: var(--line); }
.sync-go { margin-left: auto; font-size: 17px; color: var(--accent-text); }


/*
  ---------- gli spazi che nessuno aveva dichiarato ----------

  Segnalato: "alcuni elementi, per esempio gli espandi, sono troppo attaccati con gli altri".
  Misurato su dodici pagine, e non erano casi isolati: un titolo di pagina attaccato al suo
  sottotitolo, un'intestazione di carta attaccata alla riga che la spiega, un campo attaccato al
  suo suggerimento, due righe di modulo a due pixel l'una dall'altra.

  La causa e' in cima a questo file: il reset azzera il margine di tutto. E' una buona regola -
  toglie di mezzo i margini di serie del browser, che si accavallano fra loro in modi che nessuno
  ricorda - ma ha un prezzo: da li' in poi OGNI spazio va dichiarato, e quello che nessuno ha
  dichiarato non e' "quello di default", e' zero. Su cento accostamenti se ne dimenticano dieci, e
  quei dieci sono elementi che si toccano.

  Quindi si dichiarano le relazioni invece dei casi. Sono poche e si ripetono su tutte le pagine:
  un titolo e cio' che lo segue, un'intestazione e la sua spiegazione, un campo e il suo
  suggerimento, un controllo e cio' che ha intorno.

  Scritte con :where(), che vale zero di specificita'. Cosi' queste regole battono solo il reset e
  perdono contro qualunque decisione presa altrove nel file: dove qualcuno ha gia' scelto uno
  spazio, quello resta. Riempiono i buchi, non ridiscutono niente.

  Quello che NON toccano sono gli accostamenti stretti fatti apposta - un'etichetta sopra il suo
  valore, il giorno sopra la data, i due numeri di una metrica. Li' la vicinanza e' l'informazione:
  dice che quelle due cose sono una cosa sola. Il difetto non e' "poco spazio", e' poco spazio fra
  cose che non stanno insieme.
*/

/*
  Le sezioni di una pagina sono tutte alla stessa distanza fra loro.

  Segnalato con uno screenshot che cerchiava due stacchi diversi nella stessa schermata. Erano 18
  pixel dappertutto tranne tre eccezioni, ognuna nata da una regola sua: la riga "Strava collegato"
  stava a 4, la nota sotto il piano alimentare a 12. Nessuna delle tre era una decisione: erano tre
  posti in cui qualcuno ha scritto un margine senza guardare quello accanto.

  Adesso il passo e' un token, dichiarato qui e applicato a ogni figlio della colonna. Chi ha una
  ragione per staccarsi di piu' lo dichiara e vince, perche' questa regola vale zero di
  specificita'; chi non ne ha una segue il passo.
*/
:root { --passo-sezione: 18px; }
:where(.app-shell > * + *) { margin-top: var(--passo-sezione); }

/* Un titolo di pagina e la riga che lo spiega. */
:where(.page-title + *) { margin-top: 10px; }

/* Un'intestazione di carta, o il suo occhiello, e quello che viene sotto. */
:where(.card-top + *, .card-head + *) { margin-top: 12px; }
:where(.card-eyebrow + *) { margin-top: 8px; }

/* Un titolo di sezione e l'elenco che apre. */
:where(.card h3 + ul, .card h3 + ol, .card h3 + .goal-list, h3 + .conversation-list) { margin-top: 10px; }

/* Un campo e il suggerimento che lo segue; e lo stacco prima di una nuova sezione del modulo. */
:where(input + .muted, select + .muted, textarea + .muted, .pick-row + .muted,
       input + .derived, .muted + .empty, .muted + div) { margin-top: 10px; }
:where(.form-section) { margin-top: 22px; }

/* Due righe di modulo di seguito. */
/*
  Lo stacco fra un campo e il successivo, e il difetto che ci stava dentro.

  "Devono essere allineati, attualmente le input non lo sono": nel modulo per programmare a mano,
  ORA partiva dodici pixel piu' in basso di GIORNO, e CHILOMETRI piu' in basso di DISCIPLINA.

  Erano questi dodici pixel. La regola serve ai campi IMPILATI - scheda, appunto - e diceva
  ".planned-field + .planned-field", che pero' e' vero anche per due campi AFFIANCATI dentro a una
  .planned-row: il secondo di ogni coppia prendeva un margine sopra e scendeva rispetto al primo.
  Misurato in pagina prima e dopo: 26 pixel di schermo, cioe' esattamente i dodici del margine.

  "form >" e' quello che li distingue: i campi impilati sono figli diretti del modulo, quelli
  affiancati stanno dentro alla riga, e dentro alla riga la spaziatura la da' il gap.
*/
:where(.planned-row + .planned-row, .planned-row + .planned-field,
       form > .planned-field + .planned-field) { margin-top: 12px; }
:where(.planned-propose + .planned-form) { margin-top: 20px; }

/*
  Un controllo e cio' che ha intorno, che e' il caso da cui e' partita la segnalazione.

  Dodici pixel sopra e sotto: sotto quella soglia un pulsante non legge piu' come una cosa a se' -
  sembra l'ultima riga del paragrafo - e su un telefono un tocco che manca il bersaglio di due
  millimetri finisce sul testo invece che sul controllo.
*/
:where(.btn + p, .btn + .muted, button + p, button + .muted) { margin-top: 12px; }
:where(p + .btn, .muted + .btn, div + .btn) { margin-top: 12px; }
/* Sei, non quattro: il controllo e' gia' alto 44 pixel di suo - l'area minima da toccare con un
   pollice - quindi lo stacco sopra puo' essere piccolo senza che si attacchi a niente. Dodici
   sommati ai suoi quarantaquattro lo allontanavano piu' del dovuto dalla cosa che apre. */
:where(details.card-fold, details.advice-more, details.last-session-more,
       details.nutrition-history, details.memory-edit, details.session-bars) { margin-top: 6px; }

/*
  Il binario dell'accento su tutto cio' che si apre.

  "Questo bordo mettilo in tutte le sezioni che si possono espandere." Era nato sulle pieghe degli
  obiettivi: la lista rientrata di 14 pixel con una riga verticale del colore del tema accanto. Dice
  una cosa sola e la dice bene - quello che stai leggendo appartiene alla sezione che hai appena
  aperto, non alla pagina. Su una pagina dove si aprono quattro sezioni una sotto l'altra e' la
  differenza tra un elenco e un elenco dentro qualcosa.

  Una regola sola per tutte le famiglie, non una per famiglia: sono venti punti dell'app e venti
  regole sono venti occasioni di divergere.

  Il rientro sta sul <details>, non sul contenuto, perche' il contenuto non ha una forma sola - la
  zona pericolosa del profilo ha cinque figli, la piega della scheda tre, l'accordion del cibo
  undici. Un bordo per figlio sarebbe un binario spezzato in cinque tronconi con i buchi dei margini
  in mezzo. Sul contenitore e' continuo qualunque cosa ci sia dentro.

  Il riepilogo torna dov'era con un margine negativo di 16 (14 di rientro piu' i 2 del bordo): chi
  apre e chiude non deve vedere il titolo che salta a destra. Cosi' l'unica cosa che si muove
  aprendo e' il contenuto, che e' l'unica cosa che c'e' da vedere.

  Fuori restano tre cose. Due per lo stesso motivo - non sono sezioni della pagina: il selettore del
  tema nella barra in alto e' una pastiglia che apre un pannello sospeso, e "espandi" del commento
  della dashboard non ha contenuto proprio, il testo lungo e' gia' in pagina e lei lo scopre.

  Le altre due sono le pieghe che SONO la carta, e la ragione e' la stessa: il contenitore c'e' gia'
  ed e' il bordo della carta. .card-fold che porta anche .card - "cosa alleni?" negli obiettivi,
  "aggiungi un esercizio" nel tracker - avrebbe il binario sul filo sinistro della carta e il titolo,
  tirato indietro di 16, fuori dalla carta. Il monitor nutrizionale e' il corpo intero della sua
  carta ed e' aperto di suo: il binario gli correrebbe accanto per tutta l'altezza, sempre, con
  dentro altre tre pieghe che il binario ce l'hanno per davvero. Viste in pagina prima di toglierle.
*/
details.card-fold:not(.card)[open],
details.last-session-more[open],
details.nutrition-history[open],
details.memory-edit[open],
details.session-bars[open],
details.explain[open],
details.macro-edit[open],
details.planned-edit[open],
details.propose-card[open],
details.meal-log[open],
details.meal-more[open],
details.meal-edit[open],
details.diet-alt[open],
details.danger-zone[open],
details.session-when[open],
details.plan-entry[open],
details.goal-fold[open],
details.proposal-move-by-hand[open] {
  padding-left: 14px;
  border-left: 2px solid var(--accent-line);
}
details.card-fold:not(.card)[open] > summary,
details.last-session-more[open] > summary,
details.nutrition-history[open] > summary,
details.memory-edit[open] > summary,
details.session-bars[open] > summary,
details.explain[open] > summary,
details.macro-edit[open] > summary,
details.planned-edit[open] > summary,
details.propose-card[open] > summary,
details.meal-log[open] > summary,
details.meal-more[open] > summary,
details.meal-edit[open] > summary,
details.diet-alt[open] > summary,
details.danger-zone[open] > summary,
details.session-when[open] > summary,
details.plan-entry[open] > summary,
details.goal-fold[open] > summary,
details.proposal-move-by-hand[open] > summary {
  /*
    Il titolo torna dov'era e il suo testo si incolonna con il contenuto.

    Il margine negativo riporta la scatola del titolo sul filo del binario - aprendo, una piega non
    deve far saltare a destra la riga che si e' appena toccata. I sedici di rientro dentro sono la
    seconda meta': senza, il testo finiva appoggiato al binario ("modifica troppo attaccato al bordo
    arancione"), e con questi cade esattamente nella colonna di cio' che ha aperto. Quindi il binario
    abbraccia titolo e contenuto, e i due si leggono incolonnati.

    Sui titoli allineati a destra - la maggior parte, che spingono l'etichetta verso il margine
    destro - non cambia niente: e' un rientro a sinistra su una riga che a sinistra non ha niente.
  */
  margin-left: -16px;
  padding-left: 16px;
}

/* Il pulsante dei target nutrizionali resta appena dentro il binario quando il pannello e' aperto. */
details.macro-edit[open] > summary { margin-left: -8px; }

/*
  La spunta che conferma di aggiungere in fondo a una scheda che ha gia' degli esercizi.

  Riga sola, casella e frase sulla stessa linea in cima al testo e non centrate: la frase va a capo
  due volte su un telefono, e una casella centrata su due righe di testo non si capisce a cosa
  appartenga. Un po' d'aria sopra la separa dalla domanda dei giorni, che e' un'altra domanda.
*/
.write-confirm {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-top: 14px;
  font-size: 14.5px;
  line-height: 1.45;
  cursor: pointer;
}
.write-confirm input { flex: 0 0 auto; margin-top: 2px; }

/* Il collegamento all'informativa. Nella pagina dei propri dati parte dal filo del titolo; nel
   modulo di registrazione, quando segue la spunta, rientra quanto il testo della casella e si legge
   come parte di quella riga invece che come un capoverso nuovo. */
.consent-link { margin: -4px 0 14px; font-size: 14px; }
.checkbox-row + .consent-link { margin-left: 30px; }

/*
  Una nota che non e' piu' vera.

  Resta in pagina - e' roba dell'atleta, e la data in cui un fastidio e' cominciato vale anche dopo
  che e' finito - ma smette di pesare quanto le altre: testo barrato, colore piu' quieto, e sotto la
  riga che dice quando e' finita. Le aperte restano in cima, le chiuse scendono in fondo: la lista
  risponde per prima alla domanda "cosa vale adesso".
*/
.memory-closed .memory-text {
  text-decoration: line-through;
  text-decoration-color: var(--line);
  color: var(--mut2);
}
.memory-closed-note { margin: 4px 0 0; font-size: 13px; }


/* ---------- weekly weight reminder ---------- */
.weight-reminder-intro { margin: 0 0 14px; line-height: 1.5; }
.weight-reminder-form fieldset { min-width: 0; margin: 0; padding: 0; border: 0; }
.weight-reminder-days {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
}
.weight-reminder-day { position: relative; min-width: 0; cursor: pointer; }
.weight-reminder-day input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}
.weight-reminder-day span {
  display: grid;
  min-height: 44px;
  place-items: center;
  padding: 7px 5px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface-2);
  color: var(--mut);
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: .7px;
  text-transform: uppercase;
  transition: transform 120ms ease, color 140ms ease, border-color 140ms ease,
              background-color 140ms ease, box-shadow 140ms ease;
}
.weight-reminder-day:active span { transform: scale(.94); }
.weight-reminder-day input:checked + span {
  border-color: var(--accent);
  color: var(--accent-text);
  background: var(--accent-soft);
  box-shadow: 0 0 14px color-mix(in srgb, var(--accent) 16%, transparent);
}
.weight-reminder-day input:focus-visible + span {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.weight-reminder-hint { margin: 10px 0 12px; font-size: 12px; line-height: 1.45; }
.weight-reminder-form .btn { width: 100%; }
.dashboard-weight-reminder { margin-top: 0; }
.weight-reminder-summary .card-h {
  color: var(--accent-text);
  text-shadow: 0 0 12px color-mix(in srgb, var(--accent) 32%, transparent);
}


/* The weight schedule is the first, compact disclosure on /weight. Native <details> keeps it
   operable without JavaScript; the arrow and focus treatment match the other app disclosures. */
.weight-reminder-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 44px;
  list-style: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.weight-reminder-summary::-webkit-details-marker { display: none; }
.weight-reminder-summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}
.weight-reminder-card[open] > .weight-reminder-summary { margin-bottom: 14px; }
.weight-reminder-card[open] > .weight-reminder-summary .session-detail-mark {
  transform: rotate(-135deg) translateY(3px);
}
.weight-reminder-content { min-width: 0; }
@media (prefers-reduced-motion: reduce) {
  .weight-reminder-summary .session-detail-mark { transition: none; }
}

/* A scanned weekly diet is seven closed visual blocks, never one uninterrupted meal stream. */
.diet-plan-day + .diet-plan-day { margin-top: 18px; }
.diet-plan-day-label {
  margin: 4px 0 2px;
  font-family: var(--cond);
  font-size: 14px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--accent-text);
}


/* ---------- editable imported eating plan ---------- */
.diet-replace-warning {
  margin: 14px 0;
  padding: 12px 14px;
  border: 1px solid color-mix(in srgb, var(--danger) 55%, var(--line));
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--danger) 9%, transparent);
  color: var(--ink-2);
  font-size: 13px;
  line-height: 1.5;
}
.diet-scan-privacy { margin-top: 6px; }
.diet-edit { margin-top: 4px; border-top: 1px dashed var(--line-soft); }
.diet-edit > summary {
  display: flex;
  align-items: center;
  gap: 7px;
  min-height: 44px;
  cursor: pointer;
  list-style: none;
  font-family: var(--cond);
  font-size: 12px;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--accent-text);
}
.diet-edit > summary::-webkit-details-marker { display: none; }
.diet-edit[open] > summary .session-detail-mark {
  transform: rotate(-135deg) translateY(3px);
}
.diet-edit-form {
  margin: 0 0 10px;
  padding: 12px;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--surface-2) 78%, transparent);
}
.diet-edit-form label {
  margin: 0;
  font-family: var(--cond);
  font-size: 11px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--mut);
}
.diet-edit-form input[type="text"],
.diet-edit-form textarea {
  margin-top: 6px;
  padding: 11px 13px;
  font-family: var(--sans);
  font-size: 14px;
  letter-spacing: 0;
  text-transform: none;
}
.diet-edit-meal-name { display: block; margin-bottom: 12px !important; }
.diet-edit-dish {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(92px, 0.34fr);
  gap: 8px;
  padding: 10px 0;
  border-top: 1px solid var(--line-soft);
}
.diet-edit-save { width: 100%; margin-top: 12px; }


/* The scanner is a native disclosure and deliberately has no open attribute in the template. */
.diet-scan-summary {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 48px;
  cursor: pointer;
  list-style: none;
  -webkit-tap-highlight-color: transparent;
}
.diet-scan-summary::-webkit-details-marker { display: none; }
.diet-scan-summary .card-h { flex: 1 1 auto; margin: 0; }
.diet-scan-summary .session-detail-mark { flex: 0 0 auto; }
.diet-scan-card[open] > .diet-scan-summary {
  padding-bottom: 16px;
  border-bottom: 1px solid var(--line-soft);
}
.diet-scan-card[open] > .diet-scan-summary .session-detail-mark {
  transform: rotate(-135deg) translateY(3px);
}
.diet-scan-content { padding-top: 2px; }
.diet-edit-alternative {
  display: block;
  margin-top: 12px !important;
  padding-top: 12px;
  border-top: 1px solid var(--line-soft);
}
.diet-edit-alternative textarea {
  min-height: 82px;
  resize: vertical;
}


/* Only the profession word carries the selected theme; the rest of the heading stays neutral. */
.diet-scan-heading-accent {
  color: var(--accent-text);
  text-shadow: 0 0 12px color-mix(in srgb, var(--accent) 42%, transparent);
}
