/* ---------------------------------------------------------------------------
   Prépa — design system pastel.
   Mobile-first : la nav est une barre d'onglets en bas, les cartes prennent
   toute la largeur. Le desktop élargit, il ne redessine pas.

   Typo : Manrope (SIL OFL, auto-hébergée — la PWA doit marcher hors-ligne).
--------------------------------------------------------------------------- */

@font-face {
  font-family: 'Manrope';
  font-style: normal;
  font-weight: 200 800;
  font-display: swap;
  src: url("../fonts/manrope-latin-PCr8jHZ.woff2") format('woff2');
}

/* IBM Plex Mono (SIL OFL 1.1, licence dans IBMPlexMono-OFL.txt) — les
   chiffres. Auto-hébergée comme Manrope : la PWA doit marcher hors-ligne.
   Sous-ensemble latin, 13 Ko par graisse contre 48 pour la fonte complète.

   Deux graisses statiques, dont les plages couvrent TOUTE l'échelle des
   poids employés (400 à 800). Sans les plages, un `font-weight: 700` sur du
   mono déclenchait le faux gras du navigateur — un épaississement mécanique
   qui élargit les chiffres, et une colonne de valeurs ne s'alignait plus
   dès qu'une ligne passait en gras. */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url("../fonts/plex-mono-latin-400-VFEZmYn.woff2") format('woff2');
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 600 800;
  font-display: swap;
  src: url("../fonts/plex-mono-latin-600-mCd8Suj.woff2") format('woff2');
}

:root {
  /* Fond crème, texte prune douce */
  --bg: #faf7f2;
  --card: #ffffff;
  --txt: #46405a;
  /* Cette encre porte TOUS les détails de séance et toutes les légendes de
     pastille. L'ancienne (#8d86a3) plafonnait à 3,5:1 sur le blanc des
     cartes, sous le seuil AA de 4,5 : c'était là qu'était le manque de
     contraste, pas dans les pastels. */
  --muted: #665e7a;
  --line: #eee9f2;

  /* La gamme pastel */
  --lavender: #c9bcf0;
  --lavender-soft: #eae4fb;
  --mint: #a8e6c9;
  --mint-soft: #e2f8ec;
  --peach: #ffce9e;
  --peach-soft: #fff0de;
  --rose: #f7bcd4;
  --rose-soft: #fdeaf2;
  --sky: #a9d7f5;
  --sky-soft: #e4f2fc;
  --butter: #f9e08e;
  --butter-soft: #fdf6dd;

  --danger: #e8938c;
  --danger-soft: #fbe9e7;
  --ok: #7fd4a8;

  /* Encre posée SUR une teinte pleine (bouton lavande, pastille d'initiale,
     jour sélectionné). Elle était écrite en dur et accordée à la teinte
     d'origine — #3c3455 sur la lavande pastel, #2c4a3a sur la menthe. Ces
     accords ne survivent pas au changement de palette : en « vif », la
     lavande devient #a57be0 et l'encre prune n'y contraste plus ; en
     terracotta, une encre verte se retrouve posée sur un rose. Un neutre
     très sombre tient sur les six teintes des cinq palettes. */
  --on-tint: #2b2438;

  /* Encres des badges/flashs/nav — surchargées en sombre. */
  --ink-ok: #2e7d54;
  --ink-warn: #8d6f1a;
  --ink-error: #b04a42;
  --ink-pending: #3672a0;
  --nav-active: #7d68c4;

  --radius: 18px;
  --shadow: 0 1px 2px rgb(30 24 60 / 0.03), 0 12px 32px -18px rgb(120 100 160 / 0.28);
  --nav-h: 64px;

  /* --- Échelle typographique ------------------------------------------------
     Six pas, ratio ~1,25, ancrés sur un corps de 15 px. Il y en avait 22
     valeurs distinctes de 9 à 40 px, décidées écran par écran : un titre de
     section faisait 15,5 px contre 15 pour le corps de texte, donc ce n'était
     pas un titre. Les pas portent un rôle, pas une taille — `--fs-kpi` est le
     chiffre d'une pastille, il change avec elle.
  --------------------------------------------------------------------------- */
  --fs-2xs: 9.5px;  /* étiquette d'une pastille, en capitales espacées */
  --fs-xs: 11px;    /* contexte d'une pastille, mentions */
  --fs-sm: 12.5px;  /* détails, texte secondaire */
  --fs-md: 13.5px;  /* prose d'accompagnement, titre d'une ligne de séance */
  --fs-base: 15px;  /* corps */
  --fs-lg: 18px;    /* titre de section */
  --fs-xl: 25px;    /* titre de page */
  --fs-kpi: 23px;   /* chiffre d'une pastille */

  /* --- Les chiffres ---------------------------------------------------------
     Toute donnée MESURÉE (distance, durée, allure, date, dossard) passe en
     chasse fixe : les colonnes s'alignent et les chiffres ne dansent plus
     quand la valeur change d'une synchro à l'autre. Le texte reste en
     Manrope — la chasse fixe sur de la prose la rendrait illisible.
  --------------------------------------------------------------------------- */
  --mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* --- Échelle d'espacement -------------------------------------------------
     Cinq pas, base 4. Il y en avait treize valeurs (1, 2, 3, 4, 5, 6, 8, 10,
     12, 14, 16, 18, 26), d'où l'absence de rythme vertical.
  --------------------------------------------------------------------------- */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 18px;
  --s-5: 28px;
}

/* Thème sombre : appliqué par choix explicite (data-theme, posé par
   theme_controller) OU par la préférence système si aucun choix n'est fait.
   Les deux blocs sont identiques — à maintenir ensemble. */
:root[data-theme="dark"] {
  --bg: #232030; --card: #2c2839; --txt: #e9e5f2; --muted: #9d96b3; --line: #3a3549;
  --lavender-soft: #3d3654; --mint-soft: #2d4438; --peach-soft: #4a3c2b;
  --rose-soft: #47303c; --sky-soft: #2c3d4c; --butter-soft: #46402a; --danger-soft: #4a3232;
  --ink-ok: #8fdcb4; --ink-warn: #e6c86e; --ink-error: #f0a49d; --ink-pending: #93c5ea;
  --nav-active: #c9bcf0;
  --shadow: 0 2px 12px rgb(0 0 0 / 0.25);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #232030; --card: #2c2839; --txt: #e9e5f2; --muted: #9d96b3; --line: #3a3549;
    --lavender-soft: #3d3654; --mint-soft: #2d4438; --peach-soft: #4a3c2b;
    --rose-soft: #47303c; --sky-soft: #2c3d4c; --butter-soft: #46402a; --danger-soft: #4a3232;
    --ink-ok: #8fdcb4; --ink-warn: #e6c86e; --ink-error: #f0a49d; --ink-pending: #93c5ea;
    --nav-active: #c9bcf0;
    --shadow: 0 2px 12px rgb(0 0 0 / 0.25);
  }
}

/* --- Palettes choisies dans le profil -------------------------------------
   Le CSS ne connaît que `data-palette` sur <html>, posé côté serveur depuis
   le compte. Seules les six teintes d'accent changent : fond, texte et lignes
   restent ceux du thème clair/sombre, ce qui garantit la lisibilité quelle
   que soit la palette. Pastel garde ses valeurs écrites à la main plus haut —
   c'est l'original, il n'avait pas à être redessiné au passage.
--------------------------------------------------------------------------- */

:root[data-palette]:not([data-palette="pastel"]) {
  --lavender-soft: color-mix(in srgb, var(--lavender) 32%, var(--card));
  --mint-soft:     color-mix(in srgb, var(--mint) 32%, var(--card));
  --peach-soft:    color-mix(in srgb, var(--peach) 32%, var(--card));
  --rose-soft:     color-mix(in srgb, var(--rose) 32%, var(--card));
  --sky-soft:      color-mix(in srgb, var(--sky) 32%, var(--card));
  --butter-soft:   color-mix(in srgb, var(--butter) 32%, var(--card));
  --danger-soft:   color-mix(in srgb, var(--danger) 30%, var(--card));
}

/* Chaque palette est une teinte PRIMAIRE — celle qui colore la navigation et
   la barre d'avancement, donc l'identité — et cinq teintes qui s'en écartent
   de 60° sur la roue. Ce n'est pas un choix esthétique mais fonctionnel : les
   six teintes servent à coder des zones (charge, fréquence cardiaque, statuts)
   et doivent rester distinguables. Une palette « tout en bleus » ne le permet
   pas — vérifié en mesurant l'écart perceptuel (ΔE CIELAB) : un arc étroit
   tombe à ΔE 5-8 là où l'original Pastel tient 18,8. Les valeurs ci-dessous
   sont générées puis mesurées, chacune au-dessus de 17.

   Le caractère vient donc de la saturation et de la clarté, pas d'un arc de
   teintes restreint. Les variantes « soft » sont dérivées par color-mix()
   depuis --card : en sombre, elles s'adaptent d'elles-mêmes.
--------------------------------------------------------------------------- */

/* Océan — primaire bleu · ΔE min 20,2 */
:root[data-palette="ocean"] {
  --lavender: #97b8d8; --mint: #b897d8; --peach: #d897b8;
  --rose: #d8b897; --sky: #b8d897; --butter: #97d8b8;
  --nav-active: #306ba6;
}

/* Terracotta — primaire terre cuite · ΔE min 19,8 */
:root[data-palette="terracotta"] {
  --lavender: #d7a793; --mint: #c3d793; --peach: #93d7a7;
  --rose: #93c3d7; --sky: #a793d7; --butter: #d793c3;
  --nav-active: #a65430;
}

/* Forêt — primaire vert · ΔE min 17,3 */
:root[data-palette="forest"] {
  --lavender: #90cba6; --mint: #90b5cb; --peach: #a690cb;
  --rose: #cb90b5; --sky: #cba690; --butter: #b5cb90;
  --nav-active: #3a9c5e;
}

/* Vif — primaire violet, saturation haute · ΔE min 28,0 */
:root[data-palette="vif"] {
  --lavender: #a57be0; --mint: #e07bb6; --peach: #e0a57b;
  --rose: #b6e07b; --sky: #7be0a5; --butter: #7bb6e0;
  --nav-active: #6130a6;
}

/* En sombre, l'accent de navigation est le seul à porter du texte sur du
   fond : il remonte en clarté. Les deux sélecteurs suivent le double motif
   du thème (choix explicite, puis préférence système). */
:root[data-palette="ocean"][data-theme="dark"],
:root[data-palette="ocean"]:not([data-theme="light"]) { --nav-active: #97b8d8; }
:root[data-palette="terracotta"][data-theme="dark"],
:root[data-palette="terracotta"]:not([data-theme="light"]) { --nav-active: #d8aa97; }
:root[data-palette="forest"][data-theme="dark"],
:root[data-palette="forest"]:not([data-theme="light"]) { --nav-active: #9ed1b1; }
:root[data-palette="vif"][data-theme="dark"],
:root[data-palette="vif"]:not([data-theme="light"]) { --nav-active: #b08be4; }

/* …mais uniquement en sombre : `:not([data-theme="light"])` s'appliquerait
   aussi en clair sans cette remise à zéro. */
@media (prefers-color-scheme: light) {
  :root[data-palette="ocean"]:not([data-theme="dark"]) { --nav-active: #306ba6; }
  :root[data-palette="terracotta"]:not([data-theme="dark"]) { --nav-active: #a65430; }
  :root[data-palette="forest"]:not([data-theme="dark"]) { --nav-active: #3a9c5e; }
  :root[data-palette="vif"]:not([data-theme="dark"]) { --nav-active: #6130a6; }
}

/* --- Aperçu des palettes dans le profil ----------------------------------- */

.palette-grid { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
.palette-choice { display: block; position: relative; margin: 0; cursor: pointer; }
/* Le radio reste dans le flux d'accessibilité mais hors du dessin : c'est la
   bordure de la tuile qui porte l'état sélectionné. */
.palette-choice input { position: absolute; inset: 0; opacity: 0; margin: 0; }
.palette-choice .box {
  display: block;
  border: 1.5px solid var(--line);
  border-radius: 14px;
  padding: 12px;
  transition: box-shadow 0.15s, border-color 0.15s;
}
.palette-choice:hover .box { border-color: var(--muted); }
.palette-choice input:checked + .box {
  border-color: transparent;
  box-shadow: inset 0 0 0 2.5px var(--nav-active);
}
.palette-choice .swatches { display: flex; gap: 4px; margin-bottom: 8px; }
.palette-choice .swatches span { flex: 1; height: 22px; border-radius: 6px; }
.palette-choice .name { font-weight: 700; font-size: 13.5px; }
.palette-choice .desc { color: var(--muted); font-size: 11.5px; }

/* La note d'aperçu prend la teinte de la palette essayée : elle change sous
   les yeux, ce qui dit à elle seule que rien n'est encore enregistré. */
.palette-hint {
  margin: 12px 0 0;
  padding: 9px 12px;
  border-radius: 12px;
  background: var(--butter-soft);
  font-size: 12.5px;
}

* { box-sizing: border-box; }

/* `hidden` doit gagner contre les classes de mise en page. Le `display: none`
   de l'attribut vient de la feuille du navigateur : n'importe quelle classe
   qui pose `display: flex` le bat, et un bloc masqué restait affiché — les
   deux rangées de distances (route ET trail) s'affichaient en même temps. */
[hidden] { display: none !important; }

body {
  margin: 0;
  padding: 20px 16px calc(var(--nav-h) + 24px);
  background: var(--bg);
  color: var(--txt);
  font: 15px/1.55 'Manrope', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
}

main { max-width: 640px; margin: 0 auto; }

h1 { font-size: var(--fs-xl); font-weight: 800; margin: 0 0 var(--s-1); letter-spacing: -0.03em; line-height: 1.2; }
h2 { font-size: var(--fs-lg); font-weight: 750; margin: 0 0 var(--s-2); letter-spacing: -0.02em; line-height: 1.25; }
.sub { color: var(--muted); font-size: var(--fs-md); margin: 0 0 var(--s-4); }

/* --- Utilitaires de texte -------------------------------------------------
   De quoi ne plus écrire `style="font-size: 12px"` : 332 attributs de style
   en dur vivaient dans les gabarits, dont 120 dans le seul plan d'allure.
--------------------------------------------------------------------------- */

.t-xs { font-size: var(--fs-xs); }
.t-sm { font-size: var(--fs-sm); }
.t-md { font-size: var(--fs-md); }
.muted { color: var(--muted); }
/* Une donnée mesurée. Les composants qui en portent toujours une (pastille,
   jour d'une séance, cellule de tableau) l'appliquent d'eux-mêmes ; cette
   classe sert aux valeurs isolées au fil du texte. */
.num { font-family: var(--mono); font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }
.flat { margin: 0; }
.mt-1 { margin-top: var(--s-1); }
.mt-2 { margin-top: var(--s-2); }
.mt-3 { margin-top: var(--s-3); }
.mt-4 { margin-top: var(--s-4); }
.row { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
.row-tight { display: flex; align-items: baseline; gap: var(--s-2); flex-wrap: wrap; }
.push { margin-left: auto; }

/* --- Encadré --------------------------------------------------------------
   Un encadré n'est pas une carte : les callouts étaient des `.card` avec
   `box-shadow: none` imbriquées dans d'autres cartes, ce qui donnait deux
   niveaux d'élévation contradictoires. Ici, un filet coloré à gauche et un
   fond teinté — aucune élévation.
--------------------------------------------------------------------------- */

.callout {
  border-radius: 14px;
  border-left: 3px solid var(--callout-edge, var(--lavender));
  background: var(--callout-bg, var(--lavender-soft));
  padding: var(--s-3) var(--s-4);
  margin-bottom: var(--s-3);
}
.callout:last-child { margin-bottom: 0; }
.callout > :first-child { margin-top: 0; }
.callout > :last-child { margin-bottom: 0; }
.callout.warn   { --callout-edge: var(--butter); --callout-bg: var(--butter-soft); }
.callout.danger { --callout-edge: var(--danger); --callout-bg: var(--danger-soft); }
.callout.ok     { --callout-edge: var(--mint);   --callout-bg: var(--mint-soft); }
.callout.info   { --callout-edge: var(--sky);    --callout-bg: var(--sky-soft); }

/* Compléments nés de la reprise de la page prépa — chacun remplace un
   `style=` répété plusieurs fois dans le gabarit. */
.mb-1 { margin-bottom: var(--s-1); }
.mb-3 { margin-bottom: var(--s-3); }
.mt-0 { margin-top: 0; }
.pre { white-space: pre-line; }
.unit { font-size: var(--fs-md); opacity: 0.55; font-weight: 600; }
.danger-text { color: var(--ink-error); }
.btn.compact { margin-top: 0; padding: 6px var(--s-3); }
.btn.icon.tiny { width: 30px; height: 30px; font-size: var(--fs-md); }
/* Deux colonnes imposées : l'auto-fit répartirait deux pastilles en 1 + 1. */
.kpis.two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.badge.outline { background: transparent; box-shadow: inset 0 0 0 1.5px var(--line); color: var(--muted); }
.fold-line { cursor: pointer; }
.when-link { margin-left: 0; text-decoration: none; }
.session-actions {
  margin-left: auto;
  display: flex; flex-direction: column; align-items: flex-end;
  gap: var(--s-1); flex: none;
}

/* --- Cartes ---------------------------------------------------------------
   Trois poids, pas davantage, et UNE SEULE carte accentuée par écran : si
   deux blocs crient, aucun n'est entendu.
--------------------------------------------------------------------------- */

/* Neutre — le défaut, pour tout ce qui se lit sans avoir à se décider. */
.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: var(--s-4);
  margin-bottom: var(--s-3);
}
/* Le titre d'une carte respire au-dessus du contenu, et la prose qui le suit
   ne se colle pas à lui. */
.card > h2 + .sub { margin-bottom: var(--s-3); }
.card > .sub:last-child { margin-bottom: 0; }

/* Accentuée — la séance du jour, l'action principale. C'est le CONTOUR qui
   porte l'accent, pas le fond : un aplat pleine carte pèse trop lourd sur
   mobile, où la carte fait toute la largeur de l'écran. */
.card.accent {
  border: 1.5px solid var(--lavender);
  border-radius: 16px;
}

/* Signal — alerte, proposition d'adaptation. Toujours accompagné d'une
   action : un avertissement sans bouton est un reproche. */
.card.signal {
  background: var(--danger-soft);
  border: 1.5px solid var(--danger);
  border-radius: 16px;
}
.card.signal > h2 { color: var(--ink-error); }

/* --- KPI en pastilles --------------------------------------------------- */

/* auto-fit : 2 tuiles en mobile, jusqu'à 6 de front en grand écran, sans
   media query par palier. */
.kpis {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px;
  margin-bottom: 14px;
}

.kpi {
  border-radius: 16px;
  padding: 14px 16px;
}
/* Étiquette et contexte se calculent depuis l'encre de la pastille, pas
   depuis `--muted`. Une pastille est posée sur une teinte, et `--muted` est
   une valeur fixe : en sombre, l'étiquette tombait à 2,4:1 sur un fond
   `soft` — le contraste qu'on venait justement de corriger ailleurs. Un
   retrait relatif à `currentColor` suit le fond quel qu'il soit. */
.kpi .label {
  font-size: var(--fs-2xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.1em;
  color: color-mix(in srgb, currentColor 68%, transparent);
}
.kpi .value {
  font-family: var(--mono);
  font-size: var(--fs-kpi); font-weight: 600; letter-spacing: -0.02em;
  margin: 2px 0; font-variant-numeric: tabular-nums; line-height: 1.15;
}
/* Le contexte n'est jamais vide : une valeur sans échelle ne veut rien dire. */
.kpi .hint { font-size: var(--fs-xs); font-weight: 400; color: color-mix(in srgb, currentColor 72%, transparent); }
/* Une pastille dont le contenu est un texte, pas un nombre : même place, mais
   la chasse fixe et la taille du chiffre écraseraient la ligne. */
.kpi.text .value { font-family: inherit; font-size: var(--fs-md); font-weight: 700; letter-spacing: -0.01em; }

/* --- Blocs teintés ---------------------------------------------------------
   Un bloc teinté n'est jamais un aplat seul : fond en variante `soft`,
   contour en teinte pleine. Sans le liseré, deux pastilles côte à côte se
   fondent l'une dans l'autre — l'écart entre deux fonds `soft` est trop
   faible pour tenir lieu de séparation.

   Toujours par le NOM, jamais par l'hexadécimal : les six teintes changent
   avec la palette du compte, et en terracotta `--lavender` rend une terre
   cuite, `--peach` un vert. Le nom désigne un rang dans la palette, pas une
   couleur.
--------------------------------------------------------------------------- */

.tint-lavender { background: var(--lavender-soft); border: 1.5px solid var(--lavender); }
.tint-mint     { background: var(--mint-soft);     border: 1.5px solid var(--mint); }
.tint-peach    { background: var(--peach-soft);    border: 1.5px solid var(--peach); }
.tint-rose     { background: var(--rose-soft);     border: 1.5px solid var(--rose); }
.tint-sky      { background: var(--sky-soft);      border: 1.5px solid var(--sky); }
.tint-butter   { background: var(--butter-soft);   border: 1.5px solid var(--butter); }

/* --- Répartition par zone de FC --------------------------------------------- */

.zone-row { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.zone-label { font-size: 12px; font-weight: 700; color: var(--muted); width: 22px; flex: none; }
.zone-val { font-size: 12px; color: var(--muted); width: 48px; text-align: right; flex: none;
            font-variant-numeric: tabular-nums; }
.zone-row .bar { flex: 1; }
/* Du plus facile au plus dur : menthe → ciel → beurre → pêche → rose. */
.bar span.z1 { background: var(--mint); }
.bar span.z2 { background: var(--sky); }
.bar span.z3 { background: var(--butter); }
.bar span.z4 { background: var(--peach); }
.bar span.z5 { background: var(--rose); }

/* --- Boutons de ressenti ---------------------------------------------------- */

.feeling-btn {
  border: 1.5px solid var(--line);
  background: transparent;
  border-radius: 10px;
  padding: 2px 8px;
  font-size: 15px;
  line-height: 1.4;
  cursor: pointer;
  filter: grayscale(1);
  opacity: 0.5;
  transition: filter 0.15s, opacity 0.15s, transform 0.05s;
}
.feeling-btn:hover { filter: none; opacity: 1; }
.feeling-btn:active { transform: scale(0.94); }
.feeling-btn.on { filter: none; opacity: 1; border-color: transparent; }

/* --- Colonnes ---------------------------------------------------------------
   Une pile en mobile, deux colonnes dès qu'il y a la place. align-items:start
   pour que deux cartes de hauteurs différentes ne s'étirent pas. */

.cols { display: grid; gap: 14px; align-items: start; }
.cols > * { margin-bottom: 0; }
@media (min-width: 1000px) {
  .cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Une carte peut réclamer toute la largeur au milieu d'une grille. */
  .cols > .span-all { grid-column: 1 / -1; }
}

/* Une colonne de `.cols` qui contient plusieurs blocs. Sans elle, chaque
   carte devient une cellule de la grille et l'ordre de lecture part en
   zigzag : décision, analyse, décision, analyse. */
.stack > :last-child { margin-bottom: 0; }

/* --- Barres de progression (stats de prépa) --------------------------------- */

.stat-row { margin-bottom: 14px; }
.stat-row:last-child { margin-bottom: 0; }
.stat-head { display: flex; align-items: baseline; gap: 8px; font-size: var(--fs-md); margin-bottom: 5px; }
.stat-head span {
  margin-left: auto;
  font-family: var(--mono); font-weight: 600; letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}
.stat-foot { font-size: var(--fs-sm); color: var(--muted); margin-top: 4px; }

.bar {
  height: 8px;
  border-radius: 999px;
  background: var(--line);
  overflow: hidden;
}
.bar span {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--lavender);
  transition: width 0.4s ease;
}
.bar span.good, .stat-head span.good { background: var(--mint); }
.bar span.mid,  .stat-head span.mid  { background: var(--butter); }
.bar span.low,  .stat-head span.low  { background: var(--danger); }
/* Sur le chiffre, le ton doit rester lisible : couleur de texte, pas de fond. */
.stat-head span.good { background: none; color: var(--ink-ok); }
.stat-head span.mid  { background: none; color: var(--ink-warn); }
.stat-head span.low  { background: none; color: var(--ink-error); }

/* --- La ligne de séance ----------------------------------------------------
   Le composant le plus répété de l'app : anatomie constante d'un écran à
   l'autre — filet de type, titre, détail, jour.

       [filet 7px] [titre + détail] [jour en chasse fixe]

   Les tailles viennent de l'échelle (13,5 / 12,5 / 11 px) : le document de
   design demande 13,5 / 11,5 / 11, et 11,5 n'est pas un pas. Inventer une
   23e taille pour un demi-pixel est exactement ce que l'échelle existe pour
   empêcher.

   LE FILET CODE LE TYPE DE SÉANCE, PAS LE STATUT. Une couleur par type,
   constante partout : c'est ce qui permet de reconnaître une sortie longue
   d'un coup d'œil sur la frise comme sur l'accueil. Le statut, lui, se lit
   dans les trois états ci-dessous. La correspondance type → teinte est tenue
   côté PHP (`WorkoutType::tint()`), pas ici : le gabarit ne choisit pas une
   couleur, il rend celle du type.
--------------------------------------------------------------------------- */

.session {
  position: relative;
  display: flex; align-items: center; gap: var(--s-3);
  padding: 10px var(--s-3) 10px 19px;   /* 12 de marge + les 7 du filet */
  border-radius: 12px;
  background: var(--card);
}
.session + .session { margin-top: var(--s-1); }
.session::before {
  content: '';
  position: absolute; left: 0; top: 0; bottom: 0;
  width: 7px;
  border-radius: 12px 0 0 12px;
  background: var(--filet, var(--line));
}
.session .meta { min-width: 0; }
.session .name {
  font-size: var(--fs-md); font-weight: 700; line-height: 1.3;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.session .detail { color: var(--muted); font-size: var(--fs-sm); }
.session .when {
  margin-left: auto; flex: none;
  font-family: var(--mono); font-size: var(--fs-xs); font-weight: 600;
  color: var(--txt); letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}
a.when { text-decoration: none; }

/* Une ligne qui porte plus que son détail — badges, réalisé, mot du coach.
   Centrer verticalement une ligne de six hauteurs de texte ferait flotter le
   jour au milieu du vide. */
.session.stacked { align-items: flex-start; }
.session.stacked .name { white-space: normal; }

/* Faite (ou passée) — présente, mais elle ne demande plus rien. */
.session.is-done { opacity: 0.55; }
.session.is-done .when { color: var(--muted); font-weight: 600; }

/* Aujourd'hui — le fond prend la teinte du type, le filet passe à l'encre :
   quel que soit le type, la séance du jour se repère au même signe. */
.session.is-today { background: var(--filet-soft, var(--lavender-soft)); }
.session.is-today::before { background: var(--txt); }
.session.is-today .name { font-size: var(--fs-base); font-weight: 800; }
.session.is-today .when { color: var(--txt); font-weight: 700; }

/* Une séance hors plan : pas de type, donc pas de teinte — l'emoji du sport
   porte l'information à la place du filet. */
.session.off-plan { --filet: var(--line); }
.session.off-plan .name::before { content: attr(data-emoji) ' '; }

/* En grand écran, la ligne de séance passe sur cinq colonnes : le jour part
   en tête, le filet le suit, le titre et le détail se séparent, les mesures
   ferment à droite. C'est la même information — l'anatomie du §4 tient
   encore — mais alignée en colonnes d'une ligne à l'autre : on lit une
   semaine comme un tableau, ce qui est exactement ce qu'on en fait à cette
   largeur. En mobile la place manque et le jour retourne à droite.

   Aucun changement de gabarit : `display: contents` sur `.meta` promeut le
   titre et le détail au rang de colonnes de la ligne.

   Pas pour les lignes `.stacked` (celles de la frise, qui portent badges,
   réalisé et mot du coach) — cinq colonnes n'ont pas de sens sur six
   hauteurs de texte. */
@media (min-width: 900px) {
  .session:not(.stacked) {
    /* 46 du jour + 7 du filet + les gouttières */
    padding-left: var(--s-3);
    gap: var(--s-3);
  }
  .session:not(.stacked)::before {
    left: calc(var(--s-3) + 46px + var(--s-3));
    top: 50%;
    bottom: auto;
    height: 28px;
    transform: translateY(-50%);
    border-radius: 4px;
  }
  .session:not(.stacked).is-today::before { height: 32px; }
  .session:not(.stacked) .meta { display: contents; }
  .session:not(.stacked) .when {
    order: -1;
    margin-left: 0;
    width: 46px;
    /* Le filet occupe la gouttière suivante : on la réserve ici. */
    margin-right: calc(7px + var(--s-2));
  }
  .session:not(.stacked) .name { flex: none; width: 168px; }
  .session:not(.stacked) .detail { flex: 1; min-width: 0; }
}

/* Les six teintes de filet. Par le nom : en terracotta, `.tone-rose` rend un
   bleu, et c'est voulu — le nom désigne un rang dans la palette. */
/* --- Ligne à pastille d'identité -------------------------------------------
   Ce qui n'est PAS une séance et n'a donc pas de type à coder : une personne
   (coach, athlète suivi), une note de journal. La pastille porte une
   initiale ou un emoji d'état — là où la ligne de séance porte un filet.
--------------------------------------------------------------------------- */

.activity { display: flex; align-items: center; gap: var(--s-3); padding: 10px 0; border-bottom: 1px solid var(--line); }
.activity:last-child { border-bottom: none; }
.activity .dot { width: 38px; height: 38px; border-radius: 12px; display: grid; place-items: center; font-size: 17px; flex: none; }
.activity .meta { min-width: 0; }
.activity .name { font-weight: 600; font-size: var(--fs-md); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.activity .detail { color: var(--muted); font-size: var(--fs-sm); }
.activity .when { margin-left: auto; color: var(--muted); font-size: var(--fs-xs); flex: none; font-family: var(--mono); }

/* Le titre de la séance du jour, dans la carte accentuée : c'est la phrase
   qu'on vient lire, elle ne se contente pas de la taille d'un titre de
   section. */
.today-title { font-size: var(--fs-lg); font-weight: 750; letter-spacing: -0.02em; line-height: 1.25; }

/* --- L'en-tête de l'accueil -------------------------------------------------
   La date, la semaine dans le plan, la phase : l'écran dit QUAND on est avant
   de dire quoi faire. « Sortie longue de 28 km » ne se juge pas de la même
   façon en semaine de base et en semaine de pic.
--------------------------------------------------------------------------- */

/* `.page-head` fige ses actions en `flex: 0 0 auto` : sur 390 px, la pastille
   de course et les deux boutons débordaient de l'écran. Ici elles passent à
   la ligne et peuvent rétrécir. */
/* `.page-head .actions` est déclaré plus bas dans la feuille et pose
   `flex: 0 0 auto` : à specificité égale, c'est lui qui gagnait. La barre ne
   pouvait donc pas rétrécir, sa largeur valait celle de son contenu, et le
   `flex-wrap: wrap` ne se déclenchait jamais — les trois pastilles sortaient
   de l'écran en 390 px. D'où le sélecteur plus spécifique ici. */
.page-head.home-head { align-items: flex-start; margin-bottom: var(--s-4); flex-wrap: wrap; }
.page-head.home-head .titles { flex: 1 1 240px; }
.page-head.home-head .actions { flex: 1 1 auto; flex-wrap: wrap; align-items: center; min-width: 0; }
.home-head .race-chip { min-width: 0; }
.home-head .race-chip .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* La course en cours, atteignable depuis l'en-tête. Le J-N est la seule
   chose qu'on y cherche vraiment. */
.race-chip {
  display: inline-flex; align-items: center; gap: var(--s-2);
  padding: 9px var(--s-3);
  border: 1px solid var(--line);
  border-radius: 13px;
  background: var(--card);
  text-decoration: none;
  color: var(--txt);
  font-size: var(--fs-md);
}
.race-chip:hover { border-color: var(--nav-active); }
.race-chip .name { font-weight: 700; }
.race-chip .num { color: var(--nav-active); font-weight: 600; }

/* --- La carte du jour -------------------------------------------------------
   Le seul bloc accentué de l'accueil. Trois mesures et une action : c'est
   tout ce qu'on demande à un écran ouvert le matin, avant de lacer.
--------------------------------------------------------------------------- */

.today-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-2); margin-bottom: var(--s-2); }

/* Le sur-titre : en capitales espacées et en chasse fixe, il se lit comme
   une étiquette et non comme le début de la phrase. */
.eyebrow {
  font-family: var(--mono);
  font-size: var(--fs-2xs); font-weight: 600;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--txt);
}

/* « sur la montre » : l'encre du texte en fond. Un badge teinté de plus
   passerait pour une des six teintes de code, alors que c'est un état. */
.pill-dark {
  background: var(--txt); color: var(--bg);
  font-size: var(--fs-xs); font-weight: 700;
  padding: 4px 11px; border-radius: 20px;
  white-space: nowrap;
}

.metric-strip {
  display: flex; gap: var(--s-5);
  margin-top: var(--s-3);
  padding-top: var(--s-3);
  border-top: 1px solid var(--lavender-soft);
}
.metric { display: flex; flex-direction: column; min-width: 0; }
.metric .v {
  font-family: var(--mono);
  font-size: var(--fs-lg); font-weight: 600; letter-spacing: -0.02em;
  line-height: 1.1;
}
/* Une mesure dont la valeur est un signe et non un nombre. */
.metric .v.txt { font-family: inherit; }
.metric .k { font-size: var(--fs-xs); color: var(--muted); }

.today-actions { display: flex; align-items: stretch; gap: var(--s-2); margin-top: var(--s-3); }
.today-actions .grow { flex: 1; min-width: 0; }
.today-actions .btn.block { margin-top: 0; }
.today-actions .btn.icon { width: 46px; height: auto; flex: none; }

/* L'action principale de la carte accentuée prend la teinte primaire et non
   la lavande des boutons ordinaires : c'est LE bouton de l'écran, et le
   système ne lui en autorise qu'un. */
.card.accent .today-actions .btn:not(.ghost):not(.icon) {
  background: var(--nav-active);
  color: var(--bg);
}

/* --- Carte à liste ----------------------------------------------------------
   Une carte dont le contenu EST une liste de séances : l'en-tête se sépare
   du corps, les lignes se touchent.
--------------------------------------------------------------------------- */

.list-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s-3); flex-wrap: wrap;
  padding-bottom: var(--s-3);
  margin-bottom: var(--s-2);
  border-bottom: 1px solid var(--line);
}
.list-head.flat { border-bottom: none; padding-bottom: 0; }
.list-head .pct { font-family: var(--mono); font-weight: 600; color: var(--nav-active); }

/* --- La mini-frise de l'accueil ---------------------------------------------
   La forme du plan en une ligne. Même lecture que la frise de /plan-b
   (hauteur = volume, teinte = phase), mais elle ne sélectionne rien : sur
   l'accueil on situe la semaine, on ne navigue pas dans le plan.
--------------------------------------------------------------------------- */

.mini-frieze { display: flex; align-items: flex-end; gap: 2px; height: 40px; }
.mini-bar {
  flex: 1; min-width: 0;
  background: var(--filet);
  border-radius: 3px 3px 1px 1px;
  opacity: 0.5;
}
.mini-bar.is-current { opacity: 1; box-shadow: inset 0 0 0 1.5px var(--txt); }
.mini-legend {
  display: flex; justify-content: space-between;
  margin-top: var(--s-1);
  font-family: var(--mono);
  font-size: var(--fs-2xs); letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--muted);
}

/* --- Une échéance -----------------------------------------------------------
   « Ce qui reste à passer » : la date, puis ce qui se joue ce jour-là. Trois
   au plus — c'est un horizon, pas le plan.
--------------------------------------------------------------------------- */

.milestone {
  display: flex; align-items: baseline; gap: var(--s-3);
  padding: var(--s-2) 0;
  text-decoration: none;
  color: var(--txt);
  border-bottom: 1px solid var(--line);
}
.milestone:last-child { border-bottom: none; padding-bottom: 0; }
.milestone:hover .what strong { color: var(--nav-active); }
.milestone .when {
  font-family: var(--mono); font-size: var(--fs-xs); font-weight: 600;
  color: var(--nav-active); flex: none; width: 42px;
}
.milestone .what { font-size: var(--fs-sm); line-height: 1.4; color: var(--muted); }
.milestone .what strong { color: var(--txt); }

/* Mesures de santé du jour : deux valeurs qui se consultent, sur lesquelles
   on ne décide rien. Une ligne, donc, et pas deux pastilles de plus. */
.vitals {
  margin: 0 0 var(--s-3);
  padding: 0 var(--s-1);
  font-size: var(--fs-sm);
  color: var(--muted);
}
.vitals strong { color: var(--txt); font-weight: 700; margin: 0 2px 0 4px; }
.vitals .sep { margin: 0 var(--s-2); opacity: 0.5; }

.tone-lavender { --filet: var(--lavender); --filet-soft: var(--lavender-soft); }
.tone-mint     { --filet: var(--mint);     --filet-soft: var(--mint-soft); }
.tone-peach    { --filet: var(--peach);    --filet-soft: var(--peach-soft); }
.tone-rose     { --filet: var(--rose);     --filet-soft: var(--rose-soft); }
.tone-sky      { --filet: var(--sky);      --filet-soft: var(--sky-soft); }
.tone-butter   { --filet: var(--butter);   --filet-soft: var(--butter-soft); }

/* --- Formulaires --------------------------------------------------------- */

label { display: block; font-size: var(--fs-md); font-weight: 600; margin: var(--s-4) 0 var(--s-1); }

/* Les cases et boutons radio natifs sortaient en bleu système au milieu de la
   palette : `accent-color` les y ramène sans rien réimplémenter. */
input[type="checkbox"], input[type="radio"] { accent-color: var(--nav-active); }

/* Les contrôles se stylent PAR TYPE, pas au cas par cas : `textarea` et
   `select` portaient un bloc de style en dur recopié dans chaque gabarit qui
   en contenait un — sept fois la même déclaration, et donc sept endroits à
   corriger pour changer un rayon de bordure. */
input[type="text"], input[type="email"], input[type="password"],
input[type="date"], input[type="time"], input[type="number"],
textarea, select {
  width: 100%;
  padding: 12px 14px;
  border: 1.5px solid var(--line);
  border-radius: 14px;
  background: var(--bg);
  color: var(--txt);
  font-size: var(--fs-base);
  font-family: inherit;
  transition: border-color 0.15s;
}
textarea { resize: vertical; }
input:focus, textarea:focus, select:focus { outline: none; border-color: var(--lavender); }

/* Une case à cocher habillée en pastille cliquable : les jours d'une semaine,
   le choix « durée ou distance ». Le motif était recopié à la main dans quatre
   gabarits. */
.chip-label {
  display: inline-flex; align-items: center; gap: 5px;
  margin: 0;
  padding: var(--s-2) var(--s-3);
  border: 1.5px solid var(--line);
  border-radius: 12px;
  cursor: pointer;
  font-weight: 600;
  font-size: var(--fs-md);
  width: auto;
}
.chip-label:hover { border-color: var(--muted); }
.chip-label:has(input:checked) { border-color: var(--nav-active); }

.dimmed { opacity: 0.62; }

.btn {
  display: inline-block;
  border: none;
  border-radius: 14px;
  padding: 12px 20px;
  font-size: 15px;
  font-weight: 650;
  cursor: pointer;
  background: var(--lavender);
  color: var(--on-tint);
  transition: filter 0.15s, transform 0.05s;
  text-decoration: none;
}
.btn:hover { filter: brightness(0.96); transform: translateY(-1px); box-shadow: 0 3px 10px rgb(120 100 160 / 0.18); }
.btn:active { transform: scale(0.98); box-shadow: none; }
.btn.block { display: block; width: 100%; text-align: center; margin-top: 18px; }
.btn.mint { background: var(--mint); color: var(--on-tint); }
.btn.ghost { background: transparent; color: var(--muted); box-shadow: inset 0 0 0 1.5px var(--line); }

/* Bouton picto : carré, l'icône seule. Le libellé passe en title + aria-label
   — sans ça, l'action n'existe que pour qui voit le dessin. */
.btn.icon {
  padding: 0;
  width: 40px; height: 40px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 12px;
}
.btn.icon .ico { width: 19px; height: 19px; }

/* --- En-tête de page avec actions à droite -------------------------------- */

.page-head { display: flex; align-items: flex-start; gap: 14px; }
.page-head .titles { flex: 1; min-width: 0; }
.page-head .actions { display: flex; gap: 8px; flex: 0 0 auto; }

/* Un bouton à libellé se centre sur le bloc de titre. Aligné en haut, son
   texte — centré dans ses 44 px — tombait 8 px sous celui du titre : ni
   aligné sur la ligne, ni franchement centré, donc l'air de flotter. Les
   actions en pictos gardent le haut, c'est là qu'on les veut. */
.page-head:has(.actions .btn:not(.icon)) { align-items: center; }

/* Sous 480 px, deux pictos de 40 px volent trop de place au titre. */
@media (max-width: 480px) {
  .page-head .actions .btn.icon { width: 36px; height: 36px; }
}

/* Pastille d'initiale. Le bandeau coach et le coin utilisateur la
   redimensionnent ; ce socle sert partout ailleurs (listes d'athlètes). */
.avatar {
  width: 26px; height: 26px; border-radius: 50%;
  display: inline-grid; place-items: center;
  background: var(--lavender); color: var(--on-tint);
  font-weight: 800; font-size: 12px;
  vertical-align: -5px;
}

/* --- Badges de statut ----------------------------------------------------- */

.badge {
  display: inline-flex; align-items: center; gap: 6px;
  border-radius: 999px; padding: 5px 12px;
  font-size: 12.5px; font-weight: 650;
}
.badge.ok      { background: var(--mint-soft); color: var(--ink-ok); }
.badge.warn    { background: var(--butter-soft); color: var(--ink-warn); }
.badge.error   { background: var(--danger-soft); color: var(--ink-error); }
.badge.pending { background: var(--sky-soft); color: var(--ink-pending); }

.pulse { animation: pulse 1.4s ease-in-out infinite; }
@keyframes pulse { 50% { opacity: 0.45; } }

.flash { border-radius: 14px; padding: 12px 16px; margin-bottom: 14px; font-size: 14px; }
.flash.error { background: var(--danger-soft); color: var(--ink-error); }
.flash.success { background: var(--mint-soft); color: var(--ink-ok); }

/* --- Navigation basse ------------------------------------------------------ */

nav.tabs {
  position: fixed;
  inset: auto 0 0 0;
  height: calc(var(--nav-h) + env(safe-area-inset-bottom, 0px));
  padding-bottom: env(safe-area-inset-bottom, 0px);
  background: color-mix(in srgb, var(--card) 88%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-top: 1px solid var(--line);
  display: flex;
  z-index: 10;
}
nav.tabs a {
  flex: 1;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  text-decoration: none;
  color: var(--muted);
  font-size: 10.5px; font-weight: 650;
}
nav.tabs a .ico { width: 22px; height: 22px; flex: none; }
nav.tabs a.active { color: var(--nav-active); }
nav.tabs .brand { display: none; }
/* Entrées réservées à la sidebar : six onglets est le maximum lisible en
   375 px de large. */
nav.tabs a.tab-desktop { display: none; }
@media (min-width: 900px) { nav.tabs a.tab-desktop { display: flex; } }

/* Coin utilisateur + thème : desktop uniquement (mobile a l'onglet Profil,
   et la bascule de thème vit aussi sur la page Profil). */
.side-extra { display: none; }

.theme-toggle {
  border: none;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 12.5px;
  font-weight: 650;
  cursor: pointer;
  padding: 8px 14px;
  border-radius: 12px;
  text-align: left;
}
.theme-toggle:hover { background: var(--bg); }

.user-chip {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border-radius: 14px;
  text-decoration: none;
  color: var(--txt);
}
.user-chip:hover { background: var(--bg); }
.user-chip .avatar {
  width: 34px; height: 34px;
  border-radius: 50%;
  display: grid; place-items: center;
  background: linear-gradient(135deg, var(--lavender), var(--mint));
  color: var(--on-tint);
  font-weight: 800;
  flex: none;
}
.user-chip .user-meta { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.user-chip .user-meta strong { font-size: 13.5px; }
.user-chip .user-meta small { font-size: 11px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Le marqueur des <details> de semaines : discret et cohérent. */
summary::-webkit-details-marker { display: none; }

/* --- Calendrier --------------------------------------------------------------
   Mobile : liste verticale des jours (les vides sont masqués).
   Desktop : vraie grille mensuelle 7 colonnes. Même DOM. */

.cal { display: flex; flex-direction: column; gap: 8px; }
/* L'en-tête des jours n'existe qu'en grille (desktop) : en mobile les cases
   sont empilées et chacune porte sa date. */
.cal-head {
  display: none;
  font-family: var(--mono); font-size: var(--fs-2xs);
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--muted);
  padding: 10px 12px;
}
.cal-day {
  background: var(--card);
  border-radius: 14px;
  box-shadow: var(--shadow);
  padding: 10px 12px;
}
.cal-day.empty { display: none; }
.cal-day.out { opacity: 0.5; }
/* Aujourd'hui : un filet en tête de case plutôt qu'un anneau complet. Dans
   une grille de 35 cases, l'anneau se confondait avec la cible de dépôt du
   glisser-déposer, qui en porte un aussi. */
.cal-day.today { background: var(--lavender-soft); box-shadow: inset 0 2px 0 var(--nav-active), var(--shadow); }
.cal-num { font-family: var(--mono); font-size: var(--fs-sm); font-weight: 600; color: var(--muted); margin-bottom: 4px; }
.cal-item {
  font-size: var(--fs-sm);
  font-weight: 600;
  border-radius: 8px;
  padding: 4px 7px;
  margin-top: 3px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  /* Le liseré, ici comme partout : deux pastilles empilées dans une case de
     104 px se fondaient l'une dans l'autre. */
  border: 1.5px solid transparent;
}
/* Une séance prévue prend la teinte de son TYPE, la même que son filet sur
   la frise et sur l'accueil. Elle était en lavande quel qu'il soit : le
   calendrier était le seul écran où une sortie longue ne se reconnaissait
   pas d'un coup d'œil. */
.cal-item.planned { background: var(--filet-soft, var(--lavender-soft)); border-color: var(--filet, var(--lavender)); }
.cal-item.done { background: var(--mint-soft); border-color: var(--mint); }
.cal-item.race { background: var(--peach-soft); border-color: var(--peach); font-weight: 800; }
/* Une note du journal : ce n'est pas une séance, et une douleur notée doit
   se voir dans le mois autant que le plan. */
.cal-item.note { background: var(--danger-soft); border-color: var(--danger); color: var(--ink-error); }
/* --- Mode coach --------------------------------------------------------------
   Le bandeau est volontairement voyant et permanent : écrire dans le plan
   d'un autre ne doit jamais se faire par inadvertance. */

.coach-banner {
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: 1180px;
  margin: 0 auto 16px;
  padding: 10px 14px;
  border-radius: 14px;
  background: var(--butter-soft);
  box-shadow: inset 0 0 0 1.5px var(--butter);
  font-size: 13.5px;
  flex-wrap: wrap;
}
.coach-banner .avatar {
  width: 28px; height: 28px; border-radius: 50%;
  display: grid; place-items: center; flex: none;
  background: var(--butter); color: var(--on-tint); font-weight: 800; font-size: 13px;
}
.coach-banner .sub { display: block; font-size: 12px; }
.coach-banner a { text-decoration: none; }
.coach-banner a:hover strong { text-decoration: underline; }

/* --- Frise du jour J ------------------------------------------------------ */

.race-track { position: relative; height: 110px; margin: 26px 8px 10px; }
.race-track .line {
  position: absolute; inset: auto 0;
  top: 50%; height: 6px; transform: translateY(-50%);
  border-radius: 999px;
  background: linear-gradient(90deg, var(--mint), var(--lavender) 55%, var(--peach));
}
/* La plus longue portion sans ravito, hachurée : c'est elle qui dicte
   l'autonomie à porter. */
.race-track .gap {
  position: absolute; top: 50%; height: 6px; transform: translateY(-50%);
  border-radius: 999px;
  background: repeating-linear-gradient(-45deg, rgb(0 0 0 / 0.22) 0 4px, transparent 4px 8px);
}
.race-mark { position: absolute; top: 50%; transform: translate(-50%, -50%); }
.race-mark .chip {
  width: 32px; height: 32px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--card);
  box-shadow: inset 0 0 0 2px var(--line), 0 1px 4px rgb(0 0 0 / 0.08);
  font-size: 15px;
}
.race-mark.cutoff .chip { box-shadow: inset 0 0 0 2px var(--danger), 0 1px 4px rgb(0 0 0 / 0.08); }
.race-mark.late .chip { background: var(--danger-soft); }
.race-mark .above, .race-mark .below {
  position: absolute; left: 50%; transform: translateX(-50%);
  white-space: nowrap; font-size: 10.5px;
}
.race-mark .above { bottom: 38px; font-weight: 750; }
.race-mark .below { top: 38px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* --- Infobulles ------------------------------------------------------------
   Deux mécanismes, une seule allure. `.tip` est l'infobulle générique posée
   sur <body> par tooltip_controller (texte simple, remplace l'attribut
   `title`) ; `.race-tip` est la version riche, en markup, des jalons du
   parcours — elle porte plusieurs lignes structurées qu'un attribut ne peut
   pas contenir.
--------------------------------------------------------------------------- */

.tip {
  position: fixed;
  z-index: 50;
  max-width: min(280px, calc(100vw - 24px));
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.16);
  padding: 7px 11px;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--txt);
  pointer-events: none; /* jamais entre la souris et sa cible */
}

/* L'apparition ne touche QUE la position, jamais l'opacité. Une animation
   figée sur son état de départ — onglet en arrière-plan, moteur qui la met en
   pause — laisserait une bulle totalement invisible ; là, le pire cas est un
   décalage de 3 px. Vérifié : dans un onglet masqué, l'animation reste bien
   bloquée sur `from`. */
@media (prefers-reduced-motion: no-preference) {
  .tip { animation: tip-in 0.12s ease-out; }
}
@keyframes tip-in {
  from { transform: translateY(3px); }
}
/* La flèche : un carré tourné, dont le décalage vient du contrôleur pour
   qu'elle pointe la cible même quand la bulle a été recadrée. */
.tip::after {
  content: '';
  position: absolute;
  left: var(--tip-arrow, 50%);
  width: 8px; height: 8px;
  margin-left: -4px;
  background: var(--card);
  border: 1px solid var(--line);
  transform: rotate(45deg);
}
.tip:not(.below)::after { bottom: -5px; border-top: none; border-left: none; }
.tip.below::after { top: -5px; border-bottom: none; border-right: none; }

/* Toute cible d'infobulle garde un curseur qui invite à s'arrêter dessus. */
[data-tip]:not(a):not(button):not(label):not(input) { cursor: help; }

/* Version riche des jalons du parcours : s'ouvre au survol ET au focus —
   les jalons sont tabbables, un tap mobile les focus. */
.race-mark { cursor: pointer; outline: none; }
.race-tip {
  position: absolute; bottom: 52px; left: 50%; transform: translateX(-50%) translateY(4px);
  width: max-content; max-width: 240px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.14);
  padding: 10px 13px;
  font-size: 12px; line-height: 1.5; text-align: left;
  opacity: 0; pointer-events: none;
  transition: opacity 0.12s, transform 0.12s;
  z-index: 6;
}
.race-tip::after {
  content: '';
  position: absolute; top: 100%; left: 50%; transform: translateX(-50%);
  border: 7px solid transparent; border-top-color: var(--line);
}
.race-mark:hover, .race-mark:focus { z-index: 7; }
.race-mark:hover .race-tip, .race-mark:focus .race-tip {
  opacity: 1; transform: translateX(-50%) translateY(0);
}
.race-tip .tip-title { font-weight: 750; font-size: 13px; }
.race-tip .tip-line { color: var(--muted); white-space: nowrap; }
.race-tip .tip-line strong { color: var(--txt); font-variant-numeric: tabular-nums; }
/* Près des bords, l'infobulle sortirait de la carte : on l'ancre au jalon. */
.race-mark.tip-left .race-tip { left: -14px; transform: translateX(0) translateY(4px); }
.race-mark.tip-left:hover .race-tip, .race-mark.tip-left:focus .race-tip { transform: translateX(0) translateY(0); }
.race-mark.tip-left .race-tip::after { left: 26px; }
.race-mark.tip-right .race-tip { left: auto; right: -14px; transform: translateX(0) translateY(4px); }
.race-mark.tip-right:hover .race-tip, .race-mark.tip-right:focus .race-tip { transform: translateX(0) translateY(0); }
.race-mark.tip-right .race-tip::after { left: auto; right: 26px; }
/* En étroit, les heures se chevauchent : le km suffit, le tableau détaille.
   Et les km voisins (60/67, 97/arrivée) alternent dessus/dessous. */
@media (max-width: 640px) {
  .race-mark .below { display: none; }
  .race-mark .chip { width: 26px; height: 26px; font-size: 12.5px; }
  .race-mark .above { font-size: 9.5px; bottom: 32px; }
  .race-mark.alt .above { bottom: auto; top: 32px; }
}

/* Mot du coach sur une séance : visible sans clic, mais discret. */
.coach-note {
  margin-top: 5px;
  padding: 7px 10px;
  border-radius: 10px;
  background: var(--butter-soft);
  font-size: 12.5px;
  line-height: 1.5;
  white-space: pre-line;
}
.coach-note .by { color: var(--muted); font-size: 11.5px; white-space: nowrap; }

/* --- Choix du nombre de séances + semaine type ---------------------------- */

.count-pick { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.count-pick label { margin: 0; cursor: pointer; }
.count-pick input { position: absolute; opacity: 0; pointer-events: none; }
.count-pick span {
  display: grid; place-items: center;
  width: 44px; height: 44px;
  border: 1.5px solid var(--line); border-radius: 14px;
  font-size: 17px; font-weight: 750;
  transition: box-shadow 0.15s, border-color 0.15s;
}
.count-pick label:hover span { border-color: var(--muted); }
.count-pick input:checked + span {
  border-color: transparent;
  box-shadow: inset 0 0 0 2.5px var(--nav-active);
}

.count-summary { margin: 8px 0 0; font-size: 12.5px; }

/* Pastille de choix rapide (distances courantes). Même famille visuelle que
   `.chip-label`, mais c'est un bouton : il n'y a rien à cocher. */
.pill {
  border: 1.5px solid var(--line);
  border-radius: 12px;
  background: transparent;
  color: var(--txt);
  font: inherit;
  font-size: var(--fs-md);
  font-weight: 600;
  padding: var(--s-2) var(--s-3);
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
}
.pill:hover { border-color: var(--muted); }
.pill.on { border-color: transparent; background: var(--lavender); color: var(--on-tint); }

.week-shape { display: flex; gap: 6px; flex-wrap: wrap; }
.shape-slot {
  flex: 1 1 92px;
  border-radius: 12px;
  background: var(--bg);
  box-shadow: inset 0 0 0 1.5px var(--line);
  padding: 8px 10px;
  font-size: 11.5px;
  line-height: 1.4;
}
.shape-slot strong { display: block; font-size: 12.5px; }
.shape-slot span { color: var(--muted); }

/* --- Frise des semaines ----------------------------------------------------
   Les barres SONT le sélecteur : hauteur proportionnelle au volume de la
   semaine, teinte de la phase, et un appui monte la semaine dans la carte
   dessous. Une seule semaine est dans le DOM visible à la fois.

   L'ancien rail montrait deux semaines de détail côte à côte, chacune
   défilant dans sa propre boîte de 68vh — trois zones de défilement
   imbriquées sur mobile (la page, le rail, la colonne), et le doigt ne
   savait plus laquelle il actionnait.
--------------------------------------------------------------------------- */

.week-frieze { margin-bottom: 14px; }
.frieze-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }

.frieze-bars {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 78px;
  margin-bottom: var(--s-3);
}

.frieze-bar {
  flex: 1 1 0;
  min-width: 0;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: stretch;
  gap: 3px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  font: inherit;
  /* Une barre fine reste une cible tapable : la zone de touche déborde de
     part et d'autre sans élargir le dessin. */
  position: relative;
}
.frieze-bar::after { content: ''; position: absolute; inset: -6px -2px; }

/* La barre elle-même. Sa couleur vient de `.tone-*` (la teinte de la phase,
   en aplat plein) posée sur le bouton : sur douze pixels de large, la
   variante `soft` ne se distinguerait pas du fond de page. */
.frieze-bar .fill {
  display: block;
  background: var(--filet);
  border-radius: 5px 5px 2px 2px;
  min-height: 4px;
  transition: filter 0.15s;
}

.frieze-bar .tag {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  color: var(--muted);
  text-align: center;
  line-height: 1;
}

/* Le passé s'efface sans disparaître : il reste atteignable, il ne réclame
   simplement plus l'attention. */
.frieze-bar.is-past { opacity: 0.5; }
.frieze-bar:hover .fill { filter: brightness(0.94); }

/* La semaine en cours est repérable même quand on regarde ailleurs. */
.frieze-bar.is-current .tag { color: var(--nav-active); font-weight: 700; }

/* La semaine montée dans la carte. L'anneau est sous la barre ET sous
   l'étiquette : c'est la colonne entière qui est sélectionnée. */
.frieze-bar.is-on { opacity: 1; }
.frieze-bar.is-on .fill { box-shadow: inset 0 0 0 2px var(--txt); }
.frieze-bar.is-on .tag { color: var(--txt); font-weight: 700; }

.frieze-back {
  display: block;
  margin: var(--s-2) auto 0;
  border: none;
  background: none;
  cursor: pointer;
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 650;
  color: var(--nav-active);
  padding: 6px 12px;
  border-radius: 10px;
}
.frieze-back:hover { background: var(--bg); }

.week-col {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 14px;
}
.week-col.is-past .week-body { opacity: 0.72; }

/* Le + ferme la carte : les séances au-dessus, l'ajout en bas, toujours à la
   même place d'une semaine à l'autre. */
.add-session {
  display: block;
  width: 100%;
  margin-top: 8px;
  background: transparent;
  font: inherit;
  cursor: pointer;
  text-align: center;
  padding: 8px;
  border-radius: 12px;
  border: 1.5px dashed var(--line);
  color: var(--muted);
  text-decoration: none;
  font-size: var(--fs-sm);
  font-weight: 650;
}
.add-session:hover { border-color: var(--nav-active); color: var(--nav-active); }

.week-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
.week-head h2 { font-size: var(--fs-lg); }

/* Sur un plan long, 20 barres de 12 px tiennent encore ; en dessous de
   ~7 px, la cible devient intapable et la frise passe au défilement. */
@media (max-width: 420px) {
  .frieze-bars { overflow-x: auto; scrollbar-width: none; }
  .frieze-bars::-webkit-scrollbar { display: none; }
  .frieze-bar { flex: 0 0 14px; }
}

/* --- Sélecteur de date ----------------------------------------------------
   Le champ ressemble aux autres contrôles ; le calendrier flotte au-dessus.
   Les jours non sélectionnables ne sont pas des boutons : le motif est visible
   ET la souris ne peut rien en faire.
--------------------------------------------------------------------------- */

[data-controller~="date-picker"] { position: relative; }

.date-field {
  width: 100%;
  padding: 12px 14px;
  border: 1.5px solid var(--line);
  border-radius: 14px;
  background: var(--bg);
  color: var(--txt);
  font: inherit;
  font-size: var(--fs-base);
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s;
}
.date-field::after { content: ' 🗓'; float: right; opacity: 0.5; }
.date-field:hover, .date-field:focus { outline: none; border-color: var(--lavender); }
.date-field.empty { color: var(--muted); }

.dp {
  position: absolute;
  z-index: 40;
  top: calc(100% + 6px);
  left: 0;
  width: min(300px, calc(100vw - 40px));
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 16px;
  box-shadow: 0 12px 32px rgb(0 0 0 / 0.18);
  padding: var(--s-3);
}
.dp-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--s-2); }
.dp-head strong { font-size: var(--fs-md); }
.dp-nav {
  border: none; background: transparent; cursor: pointer;
  color: var(--muted); font-size: var(--fs-lg); font-weight: 700;
  width: 28px; height: 28px; border-radius: 9px;
}
.dp-nav:hover { background: var(--bg); color: var(--txt); }
.dp-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.dp-dow { text-align: center; font-size: var(--fs-xs); color: var(--muted); padding-bottom: var(--s-1); }
.dp-day {
  border: none; background: transparent; cursor: pointer;
  font: inherit; font-size: var(--fs-sm);
  aspect-ratio: 1; border-radius: 10px;
  display: grid; place-items: center;
  color: var(--txt);
}
.dp-day:hover { background: var(--lavender-soft); }
.dp-day.today { box-shadow: inset 0 0 0 1.5px var(--line); }
.dp-day.on { background: var(--lavender); color: var(--on-tint); font-weight: 750; }
/* Un jour hors des choix possibles : visible, mais pas un bouton. */
.dp-day.off { color: var(--muted); opacity: 0.35; cursor: default; }
.dp-note { margin: var(--s-2) 0 0; font-size: var(--fs-xs); color: var(--muted); text-align: center; }

/* --- Tableaux de données --------------------------------------------------
   Le plan d'allure alignait `text-align: right` et `font-variant-numeric` sur
   chaque cellule, une quarantaine de fois. C'est pourtant une seule règle :
   dans ces tableaux, la première colonne nomme et les suivantes chiffrent.
   D'où l'alignement à droite par défaut et la première colonne à gauche, avec
   `.l` / `.r` pour les exceptions.
--------------------------------------------------------------------------- */

.data-table { width: 100%; border-collapse: collapse; font-size: var(--fs-md); }
.data-table thead th {
  font-size: var(--fs-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--muted);
  text-align: right; padding-bottom: var(--s-1);
}
.data-table th:first-child, .data-table td:first-child { text-align: left; }
.data-table th.l, .data-table td.l { text-align: left; }
.data-table th.r, .data-table td.r { text-align: right; }
.data-table th.c, .data-table td.c { text-align: center; }
.data-table tbody tr { border-top: 1px solid var(--line); }
/* Une cellule de tableau de données porte une mesure : chasse fixe, donc.
   La première colonne est l'intitulé de la ligne (un libellé, pas un
   nombre) et reste en Manrope. */
.data-table td {
  padding: 7px 0; text-align: right;
  font-family: var(--mono); font-variant-numeric: tabular-nums; letter-spacing: -0.02em;
}
.data-table td:first-child, .data-table td.l { font-family: inherit; letter-spacing: normal; }
.data-table tbody tr.total { border-top: 2px solid var(--line); font-weight: 650; }
.data-table .dim { color: var(--muted); }

/* Variante saisissable : les cellules portent des champs, pas du texte. */
.form-table td { padding: 5px 6px; }
.form-table td:first-child { padding-left: 0; }
.form-table td:last-child { padding-right: 0; }
.form-table input { margin: 0; }
.form-table input[type="text"] { text-align: right; }
.form-table td.l input[type="text"] { text-align: left; }

/* Un tableau plus large que son conteneur défile seul, sans pousser la page. */
.scroll-x { overflow-x: auto; }
.min-460 { min-width: 460px; }
.min-560 { min-width: 560px; }
.min-640 { min-width: 640px; }

.ok-text { color: var(--ink-ok); }
.nowrap { white-space: nowrap; }
.wrap { white-space: normal; }
.w-time { width: 130px; }
.w60 { width: 60px; }
.w70 { width: 70px; }
.w90 { width: 90px; }
.w110 { width: 110px; }
.thin { font-weight: 400; }
.grow { flex: 1; min-width: 220px; margin: 0; }
/* Une ligne « libellé + champ » alignée sur le bas des champs. */
.field-row { align-items: flex-end; margin-bottom: var(--s-4); }

/* Compléments de la reprise des écrans restants. Chacun remplace un `style=`
   présent au moins deux fois dans les gabarits. */
.mb-2 { margin-bottom: var(--s-2); }
.center { text-align: center; }
.right { text-align: right; }
.plain { text-decoration: none; }
.pointer { cursor: pointer; }
.btn.small { padding: 4px 10px; font-size: var(--fs-sm); }
/* Un bouton d'action posé en bout de ligne de titre. */
.btn.aside { margin-left: auto; padding: 6px var(--s-3); font-size: var(--fs-md); }
/* L'unité qui suit un grand nombre, plus petite et effacée. */
.unit-sm { font-size: var(--fs-md); opacity: 0.55; font-weight: 600; }
/* Une pastille dont le nombre est long (« 26.5 km ») dans une grille serrée. */
.kpi.compact .value { font-size: var(--fs-xl); }
/* Une zone de graphique : la hauteur est fixée pour éviter le saut de mise en
   page pendant que Chart.js s'initialise. */
.chart-box { height: 220px; }
.actions-right { margin-left: auto; display: flex; gap: var(--s-2); }
.chips { display: flex; gap: var(--s-1); flex-wrap: wrap; }
/* Un chrono (12:00:00) ne tient pas à la taille d'un nombre court. */
.kpi.time .value { font-size: var(--fs-xl); }
/* Une légende qui prend la place restante à côté d'un champ. */
.hint-beside { flex: 1; min-width: 200px; margin: 0 0 var(--s-2); }

/* --- Bracelet imprimable du jour J ---------------------------------------
   SEULE EXCEPTION à la règle « pas d'hexadécimal en dur » : ce bloc finit
   sur du papier, plié dans une poche, souvent imprimé en noir et blanc. Ses
   gris ne suivent ni le thème (un fond sombre viderait la cartouche) ni la
   palette du compte — un tableau d'allures n'a pas à changer de couleur
   parce qu'on a choisi « terracotta ». Les valeurs restent en chasse fixe :
   c'est là que l'alignement des colonnes compte le plus, on les lit en
   courant.
--------------------------------------------------------------------------- */

.page-print main { max-width: 700px; }
.print-actions { display: flex; gap: 10px; margin-bottom: 16px; }

.bracelet {
  background: #fff;
  color: #111;
  border: 1px solid #ccc;
  border-radius: 10px;
  padding: 8mm;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}
.bracelet-head { display: flex; flex-direction: column; gap: 2px; margin-bottom: 5mm; }
.bracelet-head { border-bottom: 2px solid #46405a; padding-bottom: 2mm; }
.bracelet-head strong { font-size: 15px; }
.bracelet-head span { font-size: 11px; color: #555; }
.bracelet table { width: 100%; border-collapse: collapse; font-size: 12px; }
.bracelet th, .bracelet td { padding: 1.6mm 2mm; text-align: right; }
.bracelet th.l, .bracelet td.l { text-align: left; }
.bracelet thead th {
  font-size: 9px; text-transform: uppercase; letter-spacing: 0.06em;
  color: #666; border-bottom: 1px solid #999;
}
.bracelet tbody tr { border-bottom: 1px solid #e3e3e3; }
.bracelet tbody tr.finish { border-top: 1.5px solid #999; border-bottom: none; }
.bracelet .dim { color: #777; }
.bracelet-foot { margin-top: 4mm; font-size: 9.5px; color: #555; line-height: 1.5; }

/* Le papier : plus de chrome, plus d'ombre, et surtout aucune coupure au
   milieu d'une ligne de passage. */
@media print {
  .print-actions, nav.tabs, .flash, .coach-banner { display: none !important; }
  body { background: #fff; }
  .page-print main { max-width: none; padding: 0; margin: 0; }
  .bracelet { border: none; border-radius: 0; padding: 0; }
  .bracelet tbody tr { page-break-inside: avoid; }
  .bracelet thead { display: table-header-group; }
}

/* --- Charge aiguë / chronique, semaine par semaine ----------------------- */

.acwr { display: flex; align-items: flex-end; gap: 4px; height: 130px; }
.acwr-week {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
  height: 100%;
}
.acwr-week .bar {
  width: 100%;
  border-radius: 7px 7px 3px 3px;
  background: var(--mint);
  min-height: 3px;
}
.acwr-week.zone-under .bar { background: var(--sky); }
.acwr-week.zone-watch .bar { background: var(--butter); }
.acwr-week.zone-risk  .bar { background: var(--danger); }
.acwr-week .val {
  order: -1;
  font-size: 10.5px; font-weight: 700;
  color: var(--muted); font-variant-numeric: tabular-nums;
  margin-bottom: 3px;
}
.acwr-week.zone-risk .val { color: var(--ink-error); }
.acwr-week .lbl { font-size: 10px; color: var(--muted); margin-top: 4px; }
/* Au-delà d'une dizaine de semaines, les libellés se chevauchent. */
@media (max-width: 520px) { .acwr-week .val { display: none; } }

/* Lignes du brief du jour J : un libellé fort, un détail qui se lit vite. */
.brief-item {
  border-radius: 14px;
  padding: 10px 14px;
  margin-bottom: 8px;
  font-size: 13px;
  line-height: 1.55;
}
.brief-item:last-child { margin-bottom: 0; }
.brief-item strong { display: block; font-size: 12px; margin-bottom: 1px; }

/* Profil altimétrique sous la frise — mêmes marges, les jalons tombent
   à l'aplomb de leur relief. */
.race-profile { position: relative; margin: 2px 8px 0; }
.race-profile svg { display: block; width: 100%; height: 110px; }
.race-profile .area { fill: var(--sky); opacity: 0.7; }
.race-profile .tick {
  stroke: var(--muted); stroke-dasharray: 3 4; opacity: 0.55;
  vector-effect: non-scaling-stroke;
}
.race-profile .tick.cut { stroke: var(--danger); opacity: 0.8; }
.race-profile .alt-max, .race-profile .alt-min {
  position: absolute; left: 4px;
  font-size: 10px; color: var(--muted); font-variant-numeric: tabular-nums;
}
.race-profile .alt-max { top: 2px; }
.race-profile .alt-min { bottom: 2px; }

/* Le repli de l'édition : une ligne discrète, pas un formulaire imposé. */
details.fold > summary {
  cursor: pointer;
  list-style: none;
  font-weight: 650;
  color: var(--muted);
  padding: 4px 0;
}
details.fold > summary::-webkit-details-marker { display: none; }
details.fold > summary::before { content: '▸ '; }
details.fold[open] > summary::before { content: '▾ '; }
details.fold > summary:hover { color: var(--txt); }

/* Pastilles d'affichage des coureurs suivis. */
.guest-chips { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.guest-chip {
  display: inline-flex; align-items: center; gap: 6px;
  border: 1.5px solid var(--line);
  background: var(--card);
  color: var(--txt);
  border-radius: 999px;
  padding: 5px 12px 5px 6px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 650;
  cursor: pointer;
  transition: opacity 0.15s, filter 0.15s;
}
.guest-chip:hover { border-color: var(--lavender); }
.guest-chip.off { opacity: 0.45; filter: grayscale(1); }
.guest-chip.off .guest-tag { background: var(--muted); }

/* Séance d'un coureur suivi : pointillé, pour qu'on ne la confonde jamais
   avec les siennes. */
.cal-item.guest {
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--line);
  border-left: 3px solid var(--lavender);
  display: flex; align-items: center; gap: 5px;
}
.cal-item.guest .guest-tag {
  background: var(--lavender);
  color: var(--on-tint);
  border-radius: 5px;
  padding: 0 4px;
  font-size: 10px;
  font-weight: 800;
  flex: none;
}
.cal-item[data-detail-id] { cursor: pointer; }
.cal-item[data-workout-id] { cursor: grab; }
.cal-item[data-workout-id]:active { cursor: grabbing; }
.cal-day.drop-target { box-shadow: inset 0 0 0 2px var(--mint), var(--shadow); }

@media (min-width: 700px) {
  /* minmax(0, 1fr) : sans le 0, min-width:auto laisse les items nowrap
     élargir les colonnes au-delà du conteneur → scrollbar horizontale. */
  .cal { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 6px; }
  .cal-day { min-width: 0; }
  .cal-head {
    display: block;
    font-size: 11px; font-weight: 700; text-transform: uppercase;
    letter-spacing: 0.05em; color: var(--muted); text-align: center;
    padding-bottom: 2px;
  }
  .cal-day { min-height: 92px; padding: 8px; border-radius: 12px; }
  .cal-day.empty { display: block; }
  .cal-item span { font-size: 11.5px; }
}

/* --- Desktop : la barre d'onglets devient une sidebar gauche ---------------- */

@media (min-width: 900px) {
  body { padding: 32px 32px 32px calc(200px + 32px); }
  /* Assez large pour deux colonnes, assez borné pour que le texte reste
     lisible (au-delà de ~90 caractères par ligne, l'œil décroche). */
  main { max-width: 1180px; }
  /* Le calendrier respire sur toute la largeur disponible. */
  body.page-cal main { max-width: none; }
  /* Les formulaires restent en colonne étroite : un champ de 1000 px de
     large n'aide personne à le remplir. */
  body.page-form main { max-width: 620px; }

  nav.tabs {
    inset: 0 auto 0 0;
    width: 200px;
    height: 100dvh;
    padding: 24px 12px;
    flex-direction: column;
    justify-content: flex-start;
    gap: 4px;
    border-top: none;
    border-right: 1px solid var(--line);
  }
  nav.tabs a {
    flex: none;
    flex-direction: row;
    justify-content: flex-start;
    gap: 12px;
    padding: 11px 14px;
    border-radius: 14px;
    font-size: 14px;
  }
  nav.tabs a .ico { width: 20px; height: 20px; }
  nav.tabs a.active { background: var(--lavender-soft); box-shadow: inset 3px 0 0 var(--nav-active); }
  nav.tabs a:hover:not(.active) { background: var(--bg); }
  nav.tabs .brand {
    display: block;
    font-size: 19px;
    font-weight: 800;
    letter-spacing: -0.03em;
    padding: 4px 14px 18px;
  }
  nav.tabs .brand span { color: var(--nav-active); }
  .side-extra {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin-top: auto;
    padding-top: 12px;
    border-top: 1px solid var(--line);
  }
}

/* --- Login (page sans nav) -------------------------------------------------- */

body.bare { padding-bottom: 24px; display: grid; place-items: center; min-height: 100dvh; }
body.bare main { width: min(400px, 100%); }
.logo { font-size: 40px; text-align: center; margin-bottom: 6px; }

/* --- Popup de détail (calendrier) ---------------------------------------- */

/* En bas d'écran sur mobile (portée du pouce), centrée en grand écran. Un
   clic sur une pastille du calendrier n'a pas besoin de quitter la page pour
   voir le détail d'une séance — la tooltip suffisait pour le titre, pas pour
   le prévu/réalisé. */
.sheet-backdrop {
  position: fixed; inset: 0; z-index: 60;
  background: rgba(20, 16, 12, 0.32);
  display: flex; align-items: flex-end; justify-content: center;
  animation: sheet-fade 0.15s ease-out;
}
@keyframes sheet-fade { from { opacity: 0; } }

.sheet {
  background: var(--card);
  border-radius: 20px 20px 0 0;
  width: min(480px, 100%);
  max-height: min(80dvh, 640px);
  overflow-y: auto;
  padding: var(--s-4);
  box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.18);
}
@media (min-width: 640px) {
  .sheet-backdrop { align-items: center; }
  .sheet { border-radius: var(--radius); }
}

.sheet-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: var(--s-2); }
.sheet-head h2 { margin: 0; font-size: var(--fs-lg); }
.sheet-close {
  border: none; background: none; cursor: pointer; padding: 4px;
  font-size: 20px; line-height: 1; color: var(--muted);
  border-radius: 50%; flex: 0 0 auto;
}
.sheet-close:hover { background: var(--line); }

.sheet .data-table { margin-top: var(--s-2); }

/* --- Mode course -------------------------------------------------------------
   Le seul écran qu'on lit en courant. Deux écarts au système, assumés et
   limités à cette page : les chiffres montent à 34 px, et le retard passe en
   TEINTE PLEINE et non en pastel. À la douzième heure, sous la pluie, avec
   l'écran à 40 % de luminosité et des gants, un liseré ne suffit plus.

   La barre de navigation est retirée côté gabarit : en course il n'y a rien
   d'autre à faire que lire cet écran, et six onglets sont six façons de le
   perdre d'un doigt mouillé.
--------------------------------------------------------------------------- */

body.page-race { padding-bottom: var(--s-5); }
body.page-race main { max-width: 520px; }

.race-head { display: flex; align-items: center; gap: var(--s-3); margin-bottom: var(--s-4); }
.race-head .titles { flex: 1; min-width: 0; }
.race-name { font-size: var(--fs-lg); font-weight: 800; margin: 0; letter-spacing: -0.02em; }
.race-back {
  flex: none; width: 36px; height: 36px;
  display: grid; place-items: center;
  border: 1px solid var(--line); border-radius: 12px;
  text-decoration: none; color: var(--muted);
  font-size: var(--fs-lg); line-height: 1;
}
.race-back:hover { border-color: var(--nav-active); color: var(--nav-active); }
.race-state {
  flex: none;
  font-family: var(--mono); font-size: var(--fs-2xs); font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--nav-active);
}

.race-card, .check-card { padding: var(--s-4); }

/* Le chiffre qu'on lit en courant. Hors échelle, et c'est le but. */
.race-now { display: flex; align-items: baseline; gap: var(--s-3); margin-top: var(--s-2); flex-wrap: wrap; }
.race-now .big {
  font-family: var(--mono);
  font-size: 34px; font-weight: 600; letter-spacing: -0.03em; line-height: 1;
}
.race-then { display: flex; align-items: baseline; gap: var(--s-3); margin-top: 7px; color: var(--muted); }
.race-then .num { font-size: var(--fs-lg); font-weight: 600; }
.unit-label { font-size: var(--fs-md); color: var(--muted); }
.race-place { font-size: var(--fs-lg); font-weight: 800; letter-spacing: -0.02em; margin-top: var(--s-2); }

.race-drift {
  display: flex; align-items: center; gap: var(--s-2);
  margin-top: var(--s-3); padding-top: var(--s-3);
  border-top: 1px solid var(--lavender-soft);
  font-size: var(--fs-md);
}

/* Teinte pleine, pas pastel : c'est l'information qu'on vient chercher. */
.drift-badge {
  font-family: var(--mono); font-size: var(--fs-lg); font-weight: 600;
  padding: 5px var(--s-3); border-radius: 9px;
}
.drift-badge.late  { background: var(--ink-error); color: var(--bg); }
.drift-badge.ahead { background: var(--ink-ok); color: var(--bg); }

.drift-chip {
  font-family: var(--mono); font-size: var(--fs-xs); font-weight: 600;
  padding: 3px 10px; border-radius: 20px;
}
.drift-chip.late  { background: var(--danger-soft); color: var(--ink-error); }
.drift-chip.ahead { background: var(--mint-soft); color: var(--ink-ok); }

/* Le bouton de pointage : pleine largeur et haut, il se presse en courant,
   avec un gant, sans regarder. */
.race-check { margin-top: var(--s-3); padding: 15px 22px; font-size: var(--fs-base); font-weight: 700; }
.card .race-check { background: var(--nav-active); color: var(--bg); }

.cutoff-card { display: flex; align-items: center; gap: var(--s-3); }
.cutoff-card.is-safe  { background: var(--mint-soft); border: 1.5px solid var(--mint); }
.cutoff-card.is-tight { background: var(--danger-soft); border: 1.5px solid var(--danger); }
.cutoff-card > :first-child { flex: 1; min-width: 0; }
.cutoff-margin { text-align: right; flex: none; }
.cutoff-margin .num { font-family: var(--mono); font-size: var(--fs-xl); font-weight: 600; letter-spacing: -0.02em; }

.section-label {
  font-size: var(--fs-2xs); font-weight: 700;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--muted);
  margin: var(--s-4) 0 var(--s-2);
}

/* --- La liste des points de passage ---------------------------------------
   Partagée entre l'écran du coureur et celui du suiveur. Un point franchi
   porte son heure réelle, un point à venir sa projection.
--------------------------------------------------------------------------- */

.cp-list {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 16px;
  overflow: hidden;
}
.cp {
  display: flex; align-items: center; gap: var(--s-3);
  padding: 11px 15px;
  border-bottom: 1px solid var(--line);
  font-size: var(--fs-md);
}
.cp:last-child { border-bottom: none; }
.cp.is-passed { opacity: 0.55; }
/* Le prochain point est le seul qui demande quelque chose : c'est vers lui
   qu'on court. */
.cp.is-next { background: var(--lavender-soft); font-weight: 700; }
.cp.is-finish .cp-name { font-weight: 800; }

.cp-km { flex: none; width: 34px; font-family: var(--mono); font-size: var(--fs-sm); color: var(--muted); }
.cp.is-next .cp-km { color: var(--txt); font-weight: 700; }
.cp-dot { flex: none; width: 9px; height: 9px; border-radius: 50%; border: 1.5px solid var(--line); }
.cp.is-passed .cp-dot { background: var(--ink-ok); border-color: var(--ink-ok); }
.cp.is-next .cp-dot { background: var(--nav-active); border-color: var(--nav-active); }

.cp-name { flex: 1; min-width: 0; }
.cp-name small { display: block; font-size: var(--fs-xs); font-weight: 400; color: var(--muted); }
.cp-name small.cp-cutoff { color: var(--ink-error); font-weight: 600; }

/* L'heure du plan, barrée par la projection juste à côté. En retrait : on la
   garde pour comparer, pas pour la suivre. */
.cp-plan { flex: none; font-family: var(--mono); font-size: var(--fs-sm); color: var(--muted); }
.cp-time { flex: none; font-family: var(--mono); font-size: var(--fs-md); font-weight: 600; }
.cp-time.accent { color: var(--nav-active); font-weight: 700; }

.share-link {
  width: 100%;
  font-family: var(--mono); font-size: var(--fs-sm);
  padding: 10px var(--s-3);
  border: 1.5px solid var(--line); border-radius: 12px;
  background: var(--bg); color: var(--txt);
}

.center { text-align: center; }
.topline { border-top: 1px solid var(--lavender-soft); }
.pt-3 { padding-top: var(--s-3); }

/* --- La page de la prépa, en desktop -----------------------------------------
   Les barres tiennent toute la largeur — c'est un objet du plan entier — et
   dessous la semaine ouverte prend la place, l'analyse la colonne étroite.
   1,6 contre 1 : une semaine de cinq séances a besoin de la largeur pour que
   titre et détail restent sur une ligne, quatre pastilles n'en ont pas.
--------------------------------------------------------------------------- */

.prepa-grid { display: grid; gap: 16px; align-items: start; }
.prepa-main > :last-child { margin-bottom: 0; }
.prepa-aside:empty { display: none; }

@media (min-width: 900px) {
  .prepa-grid { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); }
}

.frieze-card { padding: var(--s-4) var(--s-4) var(--s-3); }

/* La légende des phases. Sans elle, la couleur des barres serait décorative
   — et les six teintes codent, elles ne décorent pas. */
.phase-legend {
  display: flex; flex-wrap: wrap; gap: var(--s-4);
  margin-top: var(--s-3); padding-top: var(--s-2);
  border-top: 1px solid var(--line);
  font-size: var(--fs-xs); color: var(--muted);
}
.legend-item { display: flex; align-items: center; gap: 6px; }
.legend-item .swatch {
  width: 11px; height: 11px; border-radius: 3px;
  background: var(--filet-soft); border: 1.5px solid var(--filet);
}

/* --- Les écrans d'entrée -----------------------------------------------------
   Connexion, inscription, premier jour : sans menu, parce qu'il n'y a rien
   d'autre à y faire. Chacun n'a qu'une carte, donc c'est LA carte accentuée
   de l'écran — la règle est tenue par construction.
--------------------------------------------------------------------------- */

/* Le logotype. Deux emojis de coureurs ne sont pas une marque, et c'est la
   première chose qu'on voit de l'app. */
.wordmark {
  display: flex; align-items: baseline; justify-content: center; gap: 5px;
  font-size: 40px; font-weight: 800; letter-spacing: -0.04em;
  margin-bottom: var(--s-1);
}
.wordmark span { color: var(--nav-active); }

/* L'action principale d'une carte accentuée prend la teinte primaire —
   §3 du document, et ça vaut partout, pas seulement dans la carte du jour. */
.card.accent .btn:not(.ghost):not(.icon):not(.mint) {
  background: var(--nav-active);
  color: var(--bg);
}

/* Une contrainte de saisie se lit SOUS le champ : au moment de la subir, pas
   avant d'avoir commencé à taper. */
.field-hint { margin: -6px 0 var(--s-3); font-size: var(--fs-xs); color: var(--muted); }

/* Les étapes du premier jour. Numérotées et non à puces : elles ont un
   ordre, et les deux dernières n'ont aucun sens avant la première. */
.steps { list-style: none; margin: var(--s-3) 0; padding: 0; display: flex; flex-direction: column; gap: var(--s-3); }
.steps li { display: flex; align-items: flex-start; gap: var(--s-3); font-size: var(--fs-md); line-height: 1.5; color: var(--muted); }
.steps li strong { color: var(--txt); }
.steps .n {
  flex: none; width: 24px; height: 24px;
  display: grid; place-items: center;
  border-radius: 50%;
  border: 1.5px solid var(--line);
  font-family: var(--mono); font-size: var(--fs-sm); font-weight: 600;
}
.steps li.is-now { color: var(--txt); }
.steps li.is-now .n { background: var(--lavender-soft); border-color: var(--nav-active); color: var(--txt); }

/* La légende du calendrier : quatre teintes, quatre natures d'objet. Une
   case qui porte trois pastilles de couleurs différentes ne se lit pas sans
   elle. */
.cal-legend {
  display: flex; flex-wrap: wrap; gap: var(--s-4);
  margin-top: var(--s-3);
  font-size: var(--fs-xs); color: var(--muted);
}
.cal-legend .swatch-danger { background: var(--danger-soft); border-color: var(--danger); }

/* Un bouton fantôme qui porte l'action par défaut du groupe : contour en
   encre, pas en teinte — c'est un repère, pas un accent de plus. */
.btn.ghost.strong { border-color: var(--txt); color: var(--txt); font-weight: 700; }

/* Deux colonnes inégales : une liste de séances a besoin de la largeur pour
   tenir titre et détail sur une ligne, un journal de deux notes n'en a pas. */
@media (min-width: 1000px) {
  .cols.cols-wide { grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); }
}

/* Une prose d'introduction se borne : au-delà d'environ 70 caractères par
   ligne sur un écran de 1180 px, l'œil perd la ligne suivante. */
.sub-wide { max-width: 620px; }

/* Le sélecteur de jour du journal : un champ date réduit à une pastille, à
   droite du jour écrit en toutes lettres. La date lisible est le titre, le
   champ n'est que le moyen d'en changer. */
.date-chip {
  width: auto; margin: 0;
  padding: 6px 11px;
  border: 1.5px solid var(--line); border-radius: 9px;
  background: var(--card); color: var(--muted);
  font-family: var(--mono); font-size: var(--fs-sm);
}

/* Deux barres qui se COMPARENT (avancement et fidélité) se mettent côte à
   côte : empilées, on lit deux mesures successives au lieu d'un écart. */
.stat-pair { display: grid; gap: var(--s-4); }
@media (min-width: 700px) { .stat-pair { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* Deux cartes dont la seconde porte plus de texte que la première. */
@media (min-width: 1000px) {
  .cols.cols-narrow { grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); }
}

/* La ligne d'une barrière, dans le tableau des passages : c'est elle qui
   décide de la course, elle ne peut pas se lire comme les autres. */
.data-table tr.has-cutoff { background: var(--danger-soft); }
.data-table tr.has-cutoff td { font-weight: 600; }

/* La ligne critique du bracelet : un aplat gris, et un seul. Le pastel n'y a
   pas sa place — sur du papier plié dans une poche mouillée, un liseré à
   20 % de saturation disparaît, et une imprimante noir et blanc le ramène de
   toute façon au même gris que le reste. */
.bracelet tr.critical td { background: #f2ede6; }
.bracelet tr.finish td { border-top: 2px solid #46405a; }
/* Les contraintes d'un point (barrière, matériel, arrêt) en sous-ligne : en
   colonne séparée, elles mangeaient une largeur que 80 mm n'ont pas. */
.bracelet .note { display: block; font-size: 9px; color: #555; font-weight: 600; }
.bracelet table + table { margin-top: 6mm; }

/* Un formulaire plus large que ce qui l'accompagne : les champs ont besoin
   de la place, la prose qui les explique beaucoup moins. */
@media (min-width: 1000px) {
  .cols.cols-narrow-rev { grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); }
}
