/* Thème sombre par défaut : l'application se consulte surtout en salle,
   souvent en éclairage artificiel. Un thème clair reste disponible (cf.
   `[data-theme="light"]` plus bas et le réglage dans settings.js) pour qui
   le préfère en plein jour. */

:root {
  --bg: #101319;
  --surface: #171b23;
  --surface-2: #1e232d;
  --border: #2a3040;
  --text: #e8ecf4;
  --muted: #8e98ab;
  --accent: #e8613c;          /* le rouge du balisage GR */
  --accent-soft: rgba(232, 97, 60, .14);
  --ok: #4ec98a;
  --warn: #e8b23c;
  --danger: #ff8f7a;
  --info: #5b93e8;            /* séance à venir, pas encore due */
  --header-bg: rgba(16, 19, 25, .92);
  --nav-bg: rgba(23, 27, 35, .96);
  --shadow: rgba(0, 0, 0, .3);
  --radius: 14px;
  --nav-h: 62px;
}

/* Posé sur `<html>` avant même le premier paint (cf. index.html) pour éviter
   un flash sombre → clair au chargement. */
:root[data-theme="light"] {
  --bg: #f6f1e6;               /* blanc cassé, plus chaud qu'un blanc pur */
  --surface: #fbf8f0;
  --surface-2: #efe8d7;
  --border: #ddd3bc;
  --text: #221d15;
  --muted: #6c6252;
  --accent: #d8552f;
  --accent-soft: rgba(216, 85, 47, .12);
  --ok: #1f9d63;
  --warn: #a86a06;
  --danger: #c94430;
  --info: #3568c9;
  --header-bg: rgba(251, 248, 240, .88);
  --nav-bg: rgba(251, 248, 240, .92);
  --shadow: rgba(40, 34, 20, .14);
}


* { box-sizing: border-box; }

/* --------------------------------------------------------- animations */
/*
   Un seul jeu de règles, réutilisé partout plutôt qu'une transition
   ad hoc par composant : même durée courte (140-180 ms), même courbe
   (ease-out), sur les propriétés qui bougent réellement à l'usage
   (fond, bordure, couleur, ombre, position). Le survol (`:hover`) est
   toujours sous `(hover: hover)` - sans ça, un doigt qui relâche un
   bouton sur téléphone déclenche l'état de survol et il y reste collé
   jusqu'au prochain tap ailleurs, ce qui n'arrive jamais avec une vraie
   souris. `:active` (l'appui, lui, existe aussi au doigt) reste hors de
   ce garde-fou. `prefers-reduced-motion` coupe tout d'un coup : le site
   reste lisible et utilisable, juste sans le mouvement. */
button, .btn, a, .tag, .extra-cell, .slot-cell, .achievement-cell, .day, .wd,
.list-item, .menu-link {
  transition: background-color .15s ease-out, border-color .15s ease-out,
    color .15s ease-out, box-shadow .15s ease-out, transform .15s ease-out,
    opacity .15s ease-out;
}
.meter > i { transition-property: width, background-color; transition-duration: .35s; }
@media (hover: hover) and (pointer: fine) {
  button:not([disabled]):not(.slot-cell):hover, .btn:hover {
    border-color: var(--muted); box-shadow: 0 3px 10px rgba(0, 0, 0, .28);
  }
  .slot-cell:hover { background: var(--surface-2); }
  .achievement-cell:hover { background: var(--surface); }
  button.primary:not([disabled]):hover, .btn.primary:hover {
    background: rgba(232, 97, 60, .22);
    box-shadow: 0 4px 14px rgba(232, 97, 60, .2);
  }
  button.ghost:hover { background: var(--surface-2); border-color: var(--muted); }
  button.danger:hover { border-color: var(--danger); }
  .extra-cell:hover, .tag:hover { border-color: var(--muted); }
  .extra-add:hover { background: var(--accent-soft); border-color: var(--accent); }
  /* Une carte cliquable se soulève légèrement - pas une ligne de liste
     (`.list-item`), qui vit collée à ses voisines dans une carte flush :
     un décalage vertical y désaligne le filet qui la sépare de la suivante. */
  a.card.plain-link:hover, a.card:hover {
    box-shadow: 0 8px 22px rgba(0, 0, 0, .3); transform: translateY(-1px);
  }
  button.list-item:hover, a.list-item:hover, .menu-link:hover { background: var(--surface-2); }
  .table-row-link:hover { background: var(--surface-2); }
  .day.has:hover, .wd.has:hover { filter: brightness(1.12); }
  .day.grab:hover, .wd.grab:hover { border-color: var(--accent); }
  nav.bottom a:hover { color: var(--text); }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important; scroll-behavior: auto !important;
  }
}

/* Contenu qui vient de remplacer le précédent - changement d'onglet, semaine
   ou mois feuilletés, jour du calendrier nutrition sélectionné. Un fondu
   très court plutôt qu'un remplacement sec : la même idée que les fiches et
   toasts plus bas, appliquée au contenu principal. `animation` plutôt que
   `transition` : elle démarre toute seule à l'insertion dans le DOM, sans
   code JS pour faire basculer un état « avant/après ». */
@keyframes fade-in { from { opacity: 0; transform: translateY(4px); } }
.fade-in { animation: fade-in .18s ease-out; }

html, body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 16px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-text-size-adjust: 100%;
  /* Filet de sécurité : rien ne doit jamais forcer un défilement horizontal
     de la page entière, un débordement de grille sur petit écran ne doit
     produire au pire qu'un contenu coupé, jamais une page qui glisse. */
  overflow-x: hidden;
  max-width: 100%;
}

.app { padding-bottom: calc(var(--nav-h) + env(safe-area-inset-bottom)); }

h1, h2, h3 { line-height: 1.25; margin: 0 0 .4em; }
h1 { font-size: 1.5rem; }
h2 { font-size: 1.15rem; }
h3 { font-size: 1rem; }
p { margin: 0 0 .8em; }
a { color: var(--accent); }
.plain-link { text-decoration: none; color: inherit; }
.text-ok { color: var(--ok); }
.text-accent { color: var(--accent); }
/* Même taille que `.macro-stat .value` (nutrition) : deux cartes chiffre
   + repère du même gabarit ailleurs dans l'app, elles doivent peser pareil
   à l'œil plutôt que l'une ressortir plus grosse que l'autre sans raison. */
.stat-value { font-size: 1.15rem; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.muted { color: var(--muted); }
.small { font-size: .85rem; }
.center { text-align: center; }
.row { display: flex; gap: 10px; align-items: center; }
.row.wrap { flex-wrap: wrap; }
.spread { justify-content: space-between; }
.grow { flex: 1; min-width: 0; }
.stack > * + * { margin-top: 12px; }

/* ------------------------------------------------------------- structure */

header.top {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; padding: 14px 16px calc(14px + env(safe-area-inset-top));
  padding-top: calc(14px + env(safe-area-inset-top));
  background: var(--header-bg);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border);
}
header.top .title { font-weight: 650; font-size: 1.05rem; }
header.top .sub { color: var(--muted); font-size: .8rem; }

main { padding: 16px; max-width: 780px; margin: 0 auto; }

nav.bottom {
  position: fixed; inset: auto 0 0 0; z-index: 30;
  display: grid; grid-template-columns: repeat(5, 1fr);
  background: var(--nav-bg);
  backdrop-filter: blur(10px);
  border-top: 1px solid var(--border);
  padding-bottom: env(safe-area-inset-bottom);
}
nav.bottom a {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px; height: var(--nav-h); text-decoration: none;
  color: var(--muted); font-size: .68rem; letter-spacing: .01em;
}
nav.bottom a .ico { font-size: 1.25rem; line-height: 1; }
nav.bottom a[aria-current="page"] { color: var(--accent); }

/* ---------------------------------------------------------------- cartes */

/* `display: block` via :where() (spécificité nulle) : un `.card` est un
   `<a>` par endroits (cartes cliquables) - sans ça, l'élément resterait
   `inline` par défaut, cassant tout son contenu bloc à l'intérieur. Une
   spécificité nulle laisse n'importe quelle autre classe sur le même
   élément (`.row` en flex, etc.) l'emporter sans se soucier de l'ordre. */
:where(.card) { display: block; }
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
}
.card + .card { margin-top: 12px; }
.card.flush { padding: 0; overflow: hidden; }

/* Statistiques de la page Progression (cf. progress.js) : quatre cartes en
   grille 2×2. Même correctif que `.macro-grid` juste en dessous - sans lui,
   `.card + .card` pousse trois des quatre cases vers le bas dans leur ligne,
   et la grille (align-items: stretch) étire la première en conséquence. */
.stat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.stat-grid > .card + .card { margin-top: 0; }
.stat-card { display: flex; gap: 10px; align-items: center; padding: 10px 12px; }

/* Nutrition : les calories sont le chiffre qu'on regarde en premier, en
   grand - les cinq autres macros (protéines/glucides/sucre/lipides/fibres)
   partagent un gabarit commun plus modeste, étiquette/valeur/repère toujours
   les trois présents (cf. macroStat, un espace insécable en repos de
   repère) - pour rester alignées entre elles quelle que soit la longueur du
   texte. */
.macro-kcal { text-align: center; padding: 4px 0 12px; }
.macro-kcal-value { font-size: 2rem; font-weight: 700; }
.macro-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.macro-stat { padding: 12px 10px; display: flex; flex-direction: column; gap: 3px; }
/* `.card + .card` (règle générale, plus haut) ajoute un margin-top à toute
   carte précédée d'une autre carte - vrai pour 4 des 5 cases de cette grille
   (pas la première, Protéines), qui les pousse vers le bas dans leur ligne.
   La grille étirant les cases à hauteur égale (align-items: stretch, par
   défaut), Protéines se retrouvait avec une boîte visuellement plus grande
   que ses voisines. Cette grille n'empile pas ses cases, elle n'a pas besoin
   de cet espacement vertical entre elles. */
.macro-grid > .card + .card { margin-top: 0; }
.macro-stat .value { font-size: 1.15rem; font-weight: 700; }
/* Une seule ligne chacune : la fourchette de protéines (« ≈ 110-130 g ») est
   plus longue que les autres repères - la laisser passer à la ligne
   grandirait sa carte seule et désalignerait la rangée entière. */
.macro-stat-label, .macro-stat-hint {
  font-size: .7rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.macro-stat-label { font-weight: 700; text-transform: uppercase; letter-spacing: .02em; }
.macro-stat-hint { color: var(--muted); font-size: .78rem; }

/* Repas du jour à quatre créneaux fixes (cf. slotRow, nutrition.js) : une
   rangée de quatre cellules égales plutôt qu'une liste, chacune montrant
   juste l'icône, le nom du repas et ses calories - le détail complet se
   saisit dans la fiche d'ajout/modification, pas ici. */
.slot-row { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); }
.slot-cell {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 14px 4px; border: 0; border-radius: 0; background: transparent;
  border-right: 1px solid var(--border); min-width: 0;
}
.slot-cell:last-child { border-right: 0; }
/* Cellules collées les unes aux autres dans une même rangée : ni l'élévation
   ni l'ombre du survol/appui « bouton standard » ne leur vont - l'une des
   quatre se détacherait visuellement des trois autres. Juste un fond qui
   s'éclaircit, comme les jours du calendrier. */
button.slot-cell:active { transform: none; }
.slot-icon { font-size: 1.3rem; }
.slot-label {
  font-size: .68rem; color: var(--muted); max-width: 100%; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}
.slot-kcal { font-size: .85rem; font-weight: 700; }

/* Extras (cf. extrasSection, nutrition.js) : mêmes pastilles cliquables que
   les créneaux, en ligne plutôt qu'empilées - plusieurs grignotages dans la
   même journée ne doivent pas coûter une ligne chacun. */
.extra-row { display: flex; flex-wrap: wrap; gap: 8px; padding: 6px 16px 16px; }
.extra-cell {
  display: flex; align-items: center; gap: 6px; padding: 8px 12px; min-height: 0;
  border-radius: 999px; background: var(--surface-2); border: 1px solid var(--border);
  font-size: .82rem; font-weight: 600;
}
.extra-label { max-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.extra-kcal { color: var(--muted); font-weight: 500; }
.extra-add { color: var(--accent); border-style: dashed; padding: 8px 14px; }

/* Plats favoris (§ presetChip, nutrition.js) : même pilule que les extras,
   coupée en deux zones tapables - le nom applique le plat, la croix le
   supprime - plutôt qu'un simple bouton qui n'aurait pu faire que l'un des
   deux gestes à la fois. */
.preset-chip {
  display: inline-flex; align-items: stretch; border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--border); overflow: hidden;
}
.preset-chip-label {
  border: 0; border-radius: 0; background: transparent; color: var(--text);
  padding: 7px 4px 7px 12px; font-size: .82rem; font-weight: 600; min-height: 0;
}
.preset-chip-del {
  border: 0; border-left: 1px solid var(--border); border-radius: 0;
  background: transparent; color: var(--muted); padding: 7px 10px; min-height: 0;
  font-size: .72rem;
}
.preset-chip-del:active { color: var(--danger); }

/* Calendrier nutrition (cf. nutritionCalendarCard) : mêmes classes `.day` /
   `.grid7` / `.dow` que le calendrier des séances, mais un jour n'est ici que
   « renseigné » ou non - pas de statut de séance ; cf. `.day.sel` plus bas
   pour le jour dont le détail est affiché en dessous. */

/* Médaillon rond réutilisé partout où une icône a besoin d'un fond teinté
   plutôt que de rester un emoji nu - Organisation de séance (jour
   d'entraînement vs repos), accueil (programme, phase en cours), calendrier
   (statut d'une séance passée). Une seule classe de base, quelques teintes,
   plutôt qu'une variante par écran. */
.badge {
  flex: 0 0 40px; width: 40px; height: 40px; border-radius: 999px;
  display: grid; place-items: center; font-size: 1.25rem;
  background: var(--surface-2); border: 1px solid var(--border);
}
.badge-lg { flex-basis: 44px; width: 44px; height: 44px; font-size: 1.35rem; }
.badge-accent { background: var(--accent-soft); border-color: transparent; }
.badge-ok { background: rgba(78, 201, 138, .14); border-color: transparent; }
.badge-warn { background: rgba(232, 178, 60, .14); border-color: transparent; }
.card h2 { margin-bottom: .25em; }

/* Organisation de séance : une ligne par exercice sur le modèle de
   `.exercise` (l'écran de séance réel), en plus compact - la liste peut
   compter beaucoup d'exercices sur un même écran. */
.exercise.tpl-exercise { padding: 10px 12px; gap: 10px; }
.tpl-exercise .figure.thumb { width: 56px; height: 44px; flex-basis: 56px; }
.tpl-fields {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 6px;
  margin-top: 8px;
}
.tpl-fields input { min-height: 36px; padding: 6px 4px; text-align: center; font-size: .85rem; }

.tag {
  display: inline-block; padding: 2px 9px; border-radius: 999px;
  font-size: .72rem; font-weight: 600; letter-spacing: .02em;
  background: var(--surface-2); color: var(--muted); border: 1px solid var(--border);
}
.tag.accent { background: var(--accent-soft); color: var(--accent); border-color: transparent; }
.tag.ok { background: rgba(78, 201, 138, .14); color: var(--ok); border-color: transparent; }
.tag.warn { background: rgba(232, 178, 60, .14); color: var(--warn); border-color: transparent; }
.tag.danger { background: rgba(255, 143, 122, .14); color: var(--danger); border-color: transparent; }
/* Un `.tag` cliquable (plats favoris, § addMealSheet, nutrition.js) : mêmes
   proportions que le simple repère, pas la hauteur minimale d'un bouton
   standard qui le ferait déborder de la ligne. */
button.tag { min-height: 0; font: inherit; }

/* ---------------------------------------------------------------- boutons */

button, .btn {
  font: inherit; font-weight: 600; cursor: pointer;
  border-radius: 10px; border: 1px solid var(--border);
  background: var(--surface-2); color: var(--text);
  padding: 11px 16px; min-height: 44px;
  text-decoration: none; display: inline-flex; align-items: center;
  justify-content: center; gap: 8px;
}
button:active, .btn:active { transform: translateY(1px); }
/* Même famille que le bouton « ＋ Pesée » de l'accueil (cf. `.extra-add`,
   plus haut) plutôt que l'aplat orange plein de l'ancienne DA : un fond
   discret teinté d'accent, le contour et le texte qui portent la couleur - la même pastille sert de repère à l'accent partout dans l'app (badges,
   tags, add-exercise-btn), le bouton principal la reprend plutôt que
   trancher avec elle. */
button.primary, .btn.primary {
  background: var(--accent-soft); border-color: var(--accent); color: var(--accent);
}
button.ghost { background: transparent; }
button.danger { color: var(--danger); }
button.big { width: 100%; min-height: 58px; font-size: 1.05rem; border-radius: 999px; }
button[disabled] { opacity: .5; cursor: default; }

/* ------------------------------------------------------------ formulaires */

/* Une seule ligne, toujours : un libellé plus long que sa voisine (« Calories
   (kcal) » contre « Sucre (g) ») qui passerait à la ligne pousserait son champ
   plus bas que les autres cases de la même rangée - les deux ne s'aligneraient
   plus. L'ellipse coupe plutôt que de désaligner ; à cette taille les
   libellés du site tiennent de toute façon en une ligne. */
label {
  display: block; font-size: .82rem; color: var(--muted); margin-bottom: 4px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
input, select, textarea {
  font: inherit; width: 100%; color: var(--text);
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 10px; padding: 11px 12px; min-height: 44px;
}
input:focus, select:focus, textarea:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
textarea { min-height: 84px; resize: vertical; }
/* Les flèches natives du navigateur pour `type="number"` ne suivent aucune
   des deux teintes du thème (elles restent blanches sur certains navigateurs,
   invisibles ou grises selon les autres) - repère non fiable et jamais
   stylisable proprement. Le clavier numérique du téléphone (`inputmode`)
   suffit à la saisie ; les rares champs qui ont vraiment besoin d'un
   incrément gardent leurs propres boutons +/- (cf. `.stepper`). */
input[type="number"] { -moz-appearance: textfield; }
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.field + .field { margin-top: 12px; }
.fields-2 { display: grid; grid-template-columns: 1fr 1fr; column-gap: 12px; row-gap: 4px; }
/* `.field` sort de la mise en page (`display: contents`) : son libellé et
   son champ deviennent chacun des cellules de la grille, alignés en deux
   rangées propres (libellés, puis champs) plutôt qu'empilés à l'intérieur
   d'une seule cellule par colonne. Sans ça, un libellé plus long à gauche
   (« Calories (kcal) » contre « Sucre (g) ») pousse son champ plus bas que
   son voisin de droite : les deux colonnes n'ont plus le même bas, ce qui se
   lit comme une case plus haute que l'autre. */
.fields-2 .field { display: contents; }
.fields-2 .field label { margin-bottom: 0; }

/* --------------------------------------------------------------- séances */

.exercise {
  border-top: 1px solid var(--border); padding: 14px 16px;
  display: flex; gap: 12px; align-items: flex-start;
}
.exercise:first-child { border-top: 0; }
/* Réordonnement par glissement (§ attachDragReorder, workout.js) : la
   poignée coupe le geste vertical du doigt de tout défilement de page tant
   qu'on la tient - sinon les deux se disputent le même axe. */
button.small-btn.action-drag { touch-action: none; cursor: grab; }
.exercise.dragging { opacity: .35; }
.exercise.drop-target { background: var(--accent-soft); }
.exercise-drag-ghost {
  position: fixed; z-index: 60; pointer-events: none; opacity: .95;
  background: var(--surface); box-shadow: 0 10px 26px var(--shadow, rgba(0, 0, 0, .5));
  border-radius: var(--radius); border-top: 0;
}
.exercise .check {
  flex: 0 0 26px; height: 26px; border-radius: 8px; margin-top: 2px;
  border: 1px solid var(--border); background: var(--surface-2);
  display: grid; place-items: center; color: transparent;
  transition: background .15s, border-color .15s, color .15s;
}
/* La coche n'est plus cliquable : elle reflète les séries réellement
   enregistrées (§ setRows / onFillChange), donc `ok` plutôt que `accent` - c'est un constat, pas une action à faire. */
.exercise.done .check { background: var(--ok); border-color: var(--ok); color: #0b1a13; }
/* Les trois séries sont saisies mais l'objectif de répétitions n'est pas tenu
   sur chacune d'elles : à distinguer de « done » - la coche prévient qu'il
   faudra retenter le même palier, sans bloquer la validation de la séance. */
.exercise.partial .check { background: var(--warn); border-color: var(--warn); color: #1a1200; }
.exercise .name { font-weight: 600; }
.exercise .presc { color: var(--muted); font-size: .88rem; }
/* Le média (schéma + coche) d'un exercice : la coche se pose en médaillon sur
   le coin du schéma plutôt que de prendre sa propre colonne à gauche - ça
   rend à la fiche la largeur qu'elle occupait. */
.exercise-media { position: relative; flex: 0 0 auto; }
.exercise-media .check.check-overlay {
  position: absolute; left: -6px; top: -6px; margin-top: 0;
  flex: none; width: 22px; height: 22px; border-radius: 999px; font-size: .78rem;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .5);
}
.why {
  margin-top: 8px; padding: 10px 12px; border-left: 2px solid var(--accent);
  background: var(--accent-soft); border-radius: 0 8px 8px 0; font-size: .86rem;
}
button.small-btn {
  min-height: 30px; padding: 0 11px; border-radius: 999px; font-size: .78rem;
  border: 1px solid var(--border); font-weight: 650;
}
/* Deux actions bien distinctes sur chaque exercice : l'évolution consulte
   (neutre, teinte accent discrète), remplacer agit (teinte plus affirmée) - la couleur seule doit permettre de les distinguer sans lire le texte. */
button.small-btn.action-evolution {
  background: var(--accent-soft); border-color: transparent; color: var(--accent);
}
button.small-btn.action-swap {
  background: var(--surface-2); border-color: var(--border); color: var(--text);
}
button.small-btn.action-note {
  background: transparent; border-color: var(--border); color: var(--muted);
}
/* Le menu d'actions par exercice (⋯) : une pastille ronde, discrète, qui ne
   dispute pas l'attention au nom de l'exercice à côté duquel elle vit. */
button.small-btn.action-menu {
  position: relative;
  flex: 0 0 auto; width: 30px; padding: 0; border-color: transparent;
  background: transparent; color: var(--muted); font-size: 1.15rem; font-weight: 700;
}
/* Signale qu'une note existe sans jamais l'écrire en clair sur la fiche - il faut ouvrir le menu pour la lire. */
button.small-btn.action-menu.has-note::after {
  content: ''; position: absolute; top: 4px; right: 4px;
  width: 6px; height: 6px; border-radius: 999px; background: var(--accent);
}
button.small-btn.action-swap:active,
button.small-btn.action-note:active,
button.small-btn.action-evolution:active { transform: translateY(1px); }

/* Bandeau de bloc allégé : une étiquette en petites capitales plutôt qu'un
   `h3` - le nom du bloc n'a pas besoin du même poids que celui de la séance,
   juste de s'en distinguer. */
.block-head { padding: 14px 16px 8px; }
.block-head .eyebrow {
  display: inline-block; font-size: .72rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; color: var(--accent);
}
.block-head p { margin: 4px 0 0; }
/* Les blocs (échauffement / séance / étirements) partagent maintenant une
   seule carte : un filet fin plutôt qu'un espace vide sépare le suivant du
   dernier exercice qui précède. */
.block-section + .block-section { border-top: 1px solid var(--border); }

/* Bouton d'ajout unique, sous la dernière carte de bloc : un contour en
   pointillés dit « poser quelque chose ici » sans le confondre avec une carte
   pleine ni avec une action déjà en place. */
.add-exercise-btn {
  display: block; width: 100%; margin-top: 12px; padding: 13px 16px;
  border: 1px dashed var(--border); border-radius: var(--radius);
  background: transparent; color: var(--accent); font-weight: 600;
  text-align: center;
}
.add-exercise-btn:active { background: var(--accent-soft); }

/* Grille des séries : pensée pour être remplie au pouce, entre deux séries. */
.sets { display: grid; grid-template-columns: 26px 1fr 1fr; gap: 7px 8px; margin-top: 10px; align-items: center; }
.sets.sets-4 { grid-template-columns: 26px 1fr 1fr 1fr; }
/* Chronométré : la colonne du temps porte le champ et le bouton ⏱️, elle a
   besoin de plus de largeur que la colonne voisine (gainage, planche...). */
.sets.sets-timed { grid-template-columns: 26px 1.6fr 1fr; }
/* StairMaster, tapis incliné : quatre colonnes sur un écran de téléphone,
   sans stepper +/- sur les deux dernières (cf. setRows) pour qu'elles
   tiennent - sans ça, le stepper à trois éléments (−, champ, +) répété deux
   fois ne laisse plus de place et la grille déborde, coupée par
   `overflow-x: hidden`. */
.sets.sets-metric { grid-template-columns: 22px 1.3fr 1fr 1fr; gap: 6px; }
.sets.sets-metric input { padding: 6px 3px; }
/* En-tête de colonnes, une fois au-dessus de la grille : sans lui, une
   colonne de nombres nus ne dit plus laquelle est les répétitions et
   laquelle est la charge une fois les filigranes disparus. */
.sets .col-label {
  color: var(--muted); font-size: .72rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: .03em; text-align: center; padding-bottom: 4px;
  border-bottom: 1px solid var(--border);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sets .idx.col-label { text-align: left; }
/* Numéro de série : un jeton rond plutôt qu'un simple « 1. », pour que
   l'œil retrouve la ligne d'un coup d'œil dans une grille par ailleurs
   entièrement faite de chiffres. */
.sets .idx:not(.col-label) {
  color: var(--muted); font-size: .78rem; font-weight: 700; align-self: center;
  width: 22px; height: 22px; border-radius: 999px; background: var(--surface-2);
  border: 1px solid var(--border); display: grid; place-items: center;
}
/* Un `.idx` cliquable (§ setRows, workout.js) remplit la série avec ce qui
   était prescrit - même jeton rond que le simple repère, juste tapable :
   pas de bordure ni d'ombre supplémentaire qui le ferait ressortir comme un
   vrai bouton, seul l'appui (`:active`) le confirme. */
.sets button.idx {
  min-height: 0; padding: 0; cursor: pointer; font: inherit;
}
.sets button.idx:active { border-color: var(--accent); color: var(--accent); }
@media (hover: hover) and (pointer: fine) {
  .sets button.idx:hover { border-color: var(--accent); color: var(--accent); }
}
.sets input { min-height: 40px; padding: 6px 4px; text-align: center; min-width: 0; }
/* La série qui vient d'atteindre l'objectif de répétitions se voit tout de
   suite, sans attendre la coche de l'exercice entier - et si elle n'y est
   pas encore, la couleur le dit aussi plutôt que de rester muette. */
.sets input.goal-met { border-color: var(--ok); color: var(--ok); }
.sets input.goal-short { border-color: var(--warn); }

/* Boutons +/- autour d'un champ numérique (répétitions, poids, étages...).
   Le pouce touche le bouton, la valeur bouge d'un cran sans ouvrir le
   clavier du téléphone ; la saisie directe dans le champ reste possible. */
.stepper { display: flex; align-items: stretch; min-width: 0; }
.stepper input {
  border-radius: 0; border-left: 0; border-right: 0; padding: 6px 2px;
  text-align: center;
}
.stepper-btn {
  flex: 0 0 26px; width: 26px; min-height: 40px; padding: 0;
  border-radius: 0; font-size: 1rem; font-weight: 700; line-height: 1;
  color: var(--muted); background: var(--surface-2); border: 1px solid var(--border);
}
.stepper-btn:first-child { border-radius: 8px 0 0 8px; border-right: 0; }
.stepper-btn:last-child { border-radius: 0 8px 8px 0; border-left: 0; }
.stepper-btn:active { background: var(--accent-soft); color: var(--accent); }

/* --------------------------------------------------------------- RPE */

.rpe { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; }
.rpe button { min-height: 52px; font-size: 1.05rem; }
.rpe button.sel { background: var(--accent); border-color: var(--accent); color: #180d09; }

/* ------------------------------------------------------------- apparence */

/* Groupe de choix exclusif générique - deux ou trois boutons dont un seul est
   actif à la fois (thème clair/sombre, programme/variante d'un exercice...) :
   même gabarit partout plutôt qu'une variante par écran. */
.choice-row { gap: 8px; }
.choice-btn { flex: 1; }
.choice-btn.sel { background: var(--accent); border-color: var(--accent); color: #180d09; }

/* ------------------------------------------------------------- calendrier */

/* `1fr` seul a un minimum implicite de `auto` (la taille du contenu) : sur un
   téléphone étroit, sept jours dont le contenu ne peut pas se comprimer plus
   forçaient la grille - donc la carte, donc la page - à déborder. minmax(0,
   1fr) autorise chaque colonne à rétrécir sous son contenu. */
.grid7 { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 4px; }
.grid7 .dow { color: var(--muted); font-size: .7rem; text-align: center; padding-bottom: 2px; }
.day {
  aspect-ratio: 1; border-radius: 9px; border: 1px solid var(--border);
  background: var(--surface); display: grid; place-items: center;
  font-size: .8rem; color: var(--muted); text-decoration: none; position: relative;
  min-width: 0; min-height: 0; overflow: hidden; padding: 0;
  /* Le calendrier principal défile à la verticale : le glissement d'une
     séance doit rester possible sans déclencher ce défilement (cf. weekstrip,
     même contrainte pour la même raison). */
  touch-action: pan-y;
}
.day.grab { cursor: grab; }
.day.dragging { opacity: .3; }
.day.empty { border: 0; background: none; }
.day.has { color: var(--text); }
.day.rest { border-style: dashed; }
.day.planned { background: rgba(91, 147, 232, .14); border-color: rgba(91, 147, 232, .4); color: var(--info); }
.day.done { background: rgba(78, 201, 138, .18); border-color: rgba(78, 201, 138, .4); color: var(--ok); }
.day.missed { background: rgba(232, 97, 60, .12); border-color: rgba(232, 97, 60, .35); color: var(--accent); }
.day.skipped { opacity: .45; }
.day.today { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Jour sélectionné dans le calendrier nutrition (cf. nutritionCalendarCard) :
   même repère visuel que `.today`, pour un jour qui peut être n'importe où
   dans le mois, pas seulement aujourd'hui. */
.day.sel { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Après les couleurs de statut : une case occupée (planned/missed) doit
   pouvoir accueillir le contour de dépôt par-dessus la sienne, sinon
   l'échange sur le grand calendrier ne se voit jamais. */
.day.drop { border-color: var(--accent); background: rgba(232, 97, 60, .2); }
.day .k { position: absolute; bottom: 4px; font-size: .6rem; }

.legend { display: flex; flex-wrap: wrap; gap: 10px 14px; margin: 8px 0 2px; }
.legend-item { display: inline-flex; align-items: center; gap: 5px; font-size: .72rem; color: var(--muted); }
.legend-swatch {
  width: 10px; height: 10px; border-radius: 3px; border: 1px solid var(--border);
  display: inline-block; flex: 0 0 auto;
}
.legend-swatch.swatch-done { background: rgba(78, 201, 138, .35); border-color: rgba(78, 201, 138, .6); }
.legend-swatch.swatch-missed { background: rgba(232, 97, 60, .3); border-color: rgba(232, 97, 60, .55); }
.legend-swatch.swatch-planned { background: rgba(91, 147, 232, .3); border-color: rgba(91, 147, 232, .55); }
.legend-swatch.swatch-rest { background: none; border-style: dashed; }

/* ------------------------------------------- la semaine en un coup d'oeil */

.weekstrip { user-select: none; }
.wk-head { margin-bottom: 10px; }
.wk-hint { margin: 10px 0 0; }
.weekstrip .days { gap: 4px; }

.wd {
  /* Une tuile par jour : lettre, quantième, pictogramme, libellé court. */
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  padding: 6px 2px; min-height: 78px; min-width: 0; border-radius: 10px;
  border: 1px solid var(--border); background: var(--surface-2);
  color: var(--muted); font-weight: 500;
  /* Le glissement se fait à l'horizontale : le défilement vertical de la page
     doit rester possible en partant d'une tuile. */
  touch-action: pan-y;
}
.wd .num { font-size: .8rem; }
.wd .ico { font-size: 1.15rem; line-height: 1.2; }
.wd .ico.rest { opacity: .45; }
.wd .lbl {
  font-size: .58rem; max-width: 100%; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}
.wd.has { color: var(--text); }
.wd.rest { border-style: dashed; }
.wd.planned { background: rgba(91, 147, 232, .14); border-color: rgba(91, 147, 232, .4); }
.wd.done { background: rgba(78, 201, 138, .18); border-color: rgba(78, 201, 138, .4); }
.wd.missed { background: rgba(232, 97, 60, .12); border-color: rgba(232, 97, 60, .35); }
.wd.skipped { opacity: .5; }
.wd.today { outline: 2px solid var(--accent); outline-offset: 1px; }
.wd.grab { cursor: grab; }
.wd.dragging { opacity: .3; }
.wd.drop { border-color: var(--accent); background: rgba(232, 97, 60, .2); }

/* La copie qui suit le doigt. Elle ne doit jamais intercepter le pointeur,
   sinon elle se retrouve sous le curseur à la place de la tuile visée. */
.drag-ghost {
  position: fixed; z-index: 60; width: 62px; pointer-events: none;
  transform: translate(-50%, -50%); opacity: .95;
  box-shadow: 0 10px 26px rgba(0, 0, 0, .5);
}

/* ------------------------------------------------------------- nutrition */

.nut { align-items: flex-start; }
.nut-ex { margin: 8px 0 0; padding-left: 18px; }
.nut-ex li { margin-bottom: 2px; }
.nut-block > summary { cursor: pointer; list-style: none; }
.nut-block > summary::-webkit-details-marker { display: none; }
.nut-block > summary::after { content: ' ▾'; color: var(--muted); }
.nut-block[open] > summary::after { content: ' ▴'; }

/* Section repliable générique (catégories de la bibliothèque d'exercices...) :
   même caret que les blocs nutrition ci-dessus, sous un nom qui ne présume
   pas de l'écran où elle sert. */
.collapsible > summary { cursor: pointer; list-style: none; }
.collapsible > summary::-webkit-details-marker { display: none; }
.collapsible > summary::after { content: ' ▾'; color: var(--muted); float: right; }
.collapsible[open] > summary::after { content: ' ▴'; }

/* --------------------------------------------------------------- jauges */

.meter { height: 10px; border-radius: 999px; background: var(--surface-2); overflow: hidden; }
.meter > i { display: block; height: 100%; background: var(--accent); border-radius: 999px; }
.meter.ok > i { background: var(--ok); }

.ring { display: block; margin: 0 auto; }
.ring text { fill: var(--text); }

.milestone { border-top: 1px solid var(--border); padding: 13px 16px; }
.milestone:first-child { border-top: 0; }
.milestone .lbl { display: flex; justify-content: space-between; gap: 10px; margin-bottom: 7px; }

/* Jalons de préparation (cf. milestoneCell/groupCell, progress.js) : une
   grille plutôt qu'une liste - trente succès les uns sous les autres
   défileraient sans fin, en grille ils se parcourent d'un coup d'œil. Pas
   cliquable (un simple constat), mais garde le même léger survol que les
   autres pastilles du site (cf. `(hover: hover)` plus haut) pour rester
   cohérente avec le reste de l'app. */
.achievement-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.achievement-cell {
  display: flex; flex-direction: column; gap: 8px; padding: 10px;
  border-radius: 10px; background: var(--surface-2); border: 1px solid var(--border);
  min-width: 0;
}

/* --------------------------------------------------------------- tableaux */

.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: .88rem; }
th, td { text-align: left; padding: 9px 10px; border-top: 1px solid var(--border); }
th { color: var(--muted); font-weight: 600; font-size: .78rem; }
tr:first-child th, tr:first-child td { border-top: 0; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
/* Ligne de tableau cliquable (cf. loadsTable, progress.js) : même transition
   et même survol léger que les autres surfaces interactives du site. */
.table-row-link { cursor: pointer; transition: background-color .15s ease-out; }

/* --------------------------------------------------------------- divers */

.list-item {
  border-top: 1px solid var(--border); padding: 13px 16px;
  display: flex; gap: 12px; align-items: center;
}
.list-item:first-child { border-top: 0; }
/* Une ligne de liste cliquable reste une ligne : le bouton perd son cadre. */
button.list-item {
  width: 100%; border: 0; border-top: 1px solid var(--border); border-radius: 0;
  background: transparent; justify-content: flex-start; font-weight: 500;
}
button.list-item:first-child { border-top: 0; }

/* ------------------------------------------------------------------ menu */

.menu-sheet { max-width: 420px; }
.menu-head { padding-bottom: 4px; }
.menu-group + .menu-group { margin-top: 16px; }
.menu-group-label {
  font-size: .72rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted); margin: 0 2px 6px;
}
.menu-link {
  display: flex; align-items: center; gap: 12px; padding: 13px 16px;
  border-top: 1px solid var(--border); text-decoration: none; color: var(--text);
  font-weight: 550;
}
.menu-link:first-child { border-top: 0; }
.menu-link .ico { font-size: 1.2rem; line-height: 1; flex: 0 0 auto; }
.menu-link .chev { color: var(--muted); }
.menu-link[aria-current="page"] { color: var(--accent); }
.menu-link[aria-current="page"] .chev { color: var(--accent); }
.menu-link:active { background: var(--surface-2); }
/* Une action du menu qui n'est pas un lien (installer l'app) : même ligne
   que les autres, sans le cadre et le fond par défaut d'un `<button>`. */
button.menu-link {
  width: 100%; border: 0; border-radius: 0; background: transparent;
  min-height: 0; justify-content: flex-start; text-align: left; font-weight: 550;
}
/* Le texte d'un `<button>` hérite un `text-align: center` par défaut du
   navigateur - jamais posé sur un `<a>`, d'où le texte centré dans sa
   colonne au lieu de collé à gauche comme les liens du menu. */
button.menu-link .grow { text-align: left; }

/* `animation` plutôt que `transition` : elle démarre toute seule à
   l'insertion dans le DOM, sans qu'il faille de code JS pour faire
   basculer un état « avant/après » sur l'élément. */
@keyframes backdrop-in { from { opacity: 0; } }
@keyframes backdrop-out { to { opacity: 0; } }
@keyframes sheet-in { from { transform: translateY(16px); opacity: 0; } }
@keyframes sheet-out { to { transform: translateY(16px); opacity: 0; } }
.sheet-backdrop {
  position: fixed; inset: 0; z-index: 40; background: rgba(0, 0, 0, .6);
  display: flex; align-items: flex-end; justify-content: center;
  animation: backdrop-in .18s ease-out;
}
/* Posée par le JS juste avant de retirer l'élément (§ ui.js `sheet`, `menu`
   dans app.js) : le temps que l'animation joue, plutôt qu'une disparition
   sèche à l'opposé de l'arrivée en fondu juste au-dessus. */
.sheet-backdrop.closing { animation: backdrop-out .16s ease-in forwards; }
.sheet {
  width: 100%; max-width: 560px; background: var(--surface);
  border-radius: 18px 18px 0 0; border-top: 1px solid var(--border);
  padding: 20px 16px calc(20px + env(safe-area-inset-bottom));
  max-height: 88vh; overflow: auto;
  animation: sheet-in .22s ease-out;
}
.sheet-backdrop.closing .sheet { animation: sheet-out .16s ease-in forwards; }

@keyframes toast-in { from { transform: translate(-50%, 8px); opacity: 0; } }
@keyframes toast-out { to { transform: translate(-50%, 8px); opacity: 0; } }
.toast {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(var(--nav-h) + 18px); z-index: 50;
  background: var(--surface-2); border: 1px solid var(--border);
  padding: 11px 16px; border-radius: 999px; font-size: .9rem;
  box-shadow: 0 8px 26px var(--shadow, rgba(0, 0, 0, .45));
  animation: toast-in .2s ease-out;
  max-width: calc(100vw - 32px);
}
/* Avec une action (§ toast, ui.js) : le message ne pousse plus le bouton
   hors de l'écran sur un message un peu long, la pilule s'élargit au lieu de
   grandir la fenêtre horizontalement. */
.toast.row { display: flex; align-items: center; gap: 10px; padding: 7px 8px 7px 16px; }
.toast.row button { flex: 0 0 auto; }
.toast.closing { animation: toast-out .18s ease-in forwards; }

.empty { text-align: center; color: var(--muted); padding: 34px 16px; }

.login { max-width: 380px; margin: 12vh auto; padding: 0 20px; }
.login .logo {
  width: 76px; height: 76px; margin: 0 auto 14px; border-radius: 999px;
  background: var(--accent-soft); display: grid; place-items: center; font-size: 2.2rem;
}

@media (min-width: 720px) {
  .fields-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
}

/* ------------------------------------------------------- schémas d'exercice */

/* Le trait des schémas est clair : il lui faut un fond propre, pas celui de la
   carte, sinon la silhouette se perd dans le contraste du texte. */
/* Fond fixe, pas `var(--surface-2)` : les 71 schémas SVG (web/assets/exercises)
   sont tracés en traits clairs sur fond sombre, indépendamment du thème du
   site - les faire suivre le thème clair les rendrait quasi invisibles
   (trait #e9edf5 sur un fond presque blanc). */
.figure {
  display: block; width: 100%; background: #1e232d;
  border: 1px solid #2a3040; border-radius: 10px;
}
.figure.thumb { width: 84px; height: 64px; flex: 0 0 84px; object-fit: contain; }
.figure.big { max-width: 340px; margin: 4px auto 10px; }
.figure-btn { padding: 0; border: 0; background: none; min-height: 0; }
.figure-btn:active { opacity: .7; }

.fiche > summary { display: flex; gap: 12px; align-items: center; list-style: none; }
.fiche > summary::-webkit-details-marker { display: none; }

/* ------------------------------------------------------------- repos */

/* Chrono de repos/tenue : un médaillon flottant, centré et large, plutôt
   qu'une mince bande collée en bas d'écran - c'est la seule chose qu'on doit
   pouvoir lire d'un regard depuis l'autre bout d'une salle de muscu, entre
   deux séries. Pas de fond plein écran derrière : le reste de la fiche
   d'exercice reste visible (et scrollable) tout autour, seul le chrono capte
   le pointeur. */
@keyframes timer-in { from { opacity: 0; transform: translate(-50%, -50%) scale(.92); } }
@keyframes timer-out { to { opacity: 0; transform: translate(-50%, -50%) scale(.92); } }
.timer-overlay {
  position: fixed; left: 50%; top: 46%; transform: translate(-50%, -50%);
  z-index: 45; width: min(300px, calc(100vw - 48px));
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 20px;
  padding: 22px 20px calc(18px + env(safe-area-inset-bottom));
  box-shadow: 0 16px 44px var(--shadow, rgba(0, 0, 0, .5));
  text-align: center;
  animation: timer-in .22s ease-out;
}
.timer-overlay.closing { animation: timer-out .18s ease-in forwards; }
.timer-figure { width: 84px; height: 64px; object-fit: contain; }
.timer-label { color: var(--muted); font-size: .88rem; }
.timer-count {
  font-size: 3.1rem; font-weight: 750; line-height: 1; font-variant-numeric: tabular-nums;
}
.timer-overlay .meter { width: 100%; }
.timer-actions { display: flex; gap: 8px; width: 100%; }
.timer-actions button { flex: 1; }
