/* =============================================================================
   Plateforme TP MM01 — feuille de style de l'interface (espaces étudiant et
   enseignant). Palette « papier / olive / terre » reprise des corrigés pour une
   continuité visuelle avec les énoncés.
   ============================================================================= */
:root {
  --papier: #FBF7E4; --papier-2: #F5EFD3; --encre: #1F221A; --encre-douce: #4A4D40;
  --olive: #515247; --mousse: #c5c8b3; --trait: #d6d2bd;
  --accent: #C25A1F; --accent-2: #5B7B3A; --danger: #B3261E; --alerte: #B7791F;
  --ombre: 0 1px 0 rgba(31,34,26,.04), 0 6px 24px -12px rgba(31,34,26,.18);
  --rayon: 6px;
  /* Surfaces et états, nommés pour que le mode sombre puisse les redéfinir d'un bloc */
  --carte: #fff;                  /* fond des cartes, tableaux, champs de saisie */
  --survol: #efe8c8;              /* ligne de tableau survolée */
  --survol-2: #ece5c4;            /* bouton secondaire survolé */
  --accent-fonce: #a94d1a; --accent-2-fonce: #4a6630;
  --inverse-fond: var(--encre); --inverse-texte: var(--papier);  /* barre, en-têtes de tableau, bulles */
  --ok-texte: #3f5a28; --alerte-texte: #7a4f10; --danger-texte: #8a1d16;
  --nuit: #e7e2cd;                /* heures nocturnes des statistiques */
  --confirmation-fond: #fff8ec;
  --groupe-actif: #eef3e6;
  --voile: rgba(255,255,255,.1);  /* survol sur la barre supérieure */
  --voile-trait: rgba(255,255,255,.35);
  --code-ligne: rgba(31,34,26,.07); /* fond d'un bout de code au fil du texte */
  --revoir-fond: rgba(183,121,31,.07);
  --alerte-ligne: rgba(179,38,30,.06);
}
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0; min-height: 100vh; display: flex; flex-direction: column;
  font-family: "DM Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 15px; line-height: 1.55; color: var(--encre); background: var(--papier);
}
h1, h2, h3 { font-family: "Fraunces", Georgia, serif; letter-spacing: -0.01em; margin: 0 0 .6rem; }
h1 { font-size: 1.7rem; } h2 { font-size: 1.25rem; } h3 { font-size: 1.05rem; }
a { color: var(--accent); }
/* overflow-wrap : une longue suite sans espace — liste de colonnes CSV, chemin, URL — se coupe
   plutôt que de sortir de sa carte et de passer par-dessus la voisine (v3.46) */
code { font-family: "JetBrains Mono", ui-monospace, monospace; font-size: .9em; background: var(--code-ligne); padding: .1em .35em; border-radius: 3px; overflow-wrap: anywhere; }

.evitement { position: absolute; left: -9999px; }
.evitement:focus { left: 8px; top: 8px; background: var(--inverse-fond); color: var(--inverse-texte); padding: .5rem .8rem; z-index: 999; }

/* ---------- Barre supérieure ---------- */
.barre {
  position: sticky; top: 0; z-index: 50; height: 54px;
  background: var(--inverse-fond); color: var(--inverse-texte); border-bottom: 3px solid var(--accent);
  display: flex; align-items: center; gap: 1.5rem; padding: 0 1.2rem;
}
.barre__marque { font-family: "Fraunces", serif; font-weight: 700; color: var(--inverse-texte); text-decoration: none; font-size: 1.05rem; }
.barre__marque em { color: var(--accent); font-style: italic; }
.barre__nav { display: flex; gap: .2rem; flex: 1; }
.barre__nav a { color: var(--inverse-texte); text-decoration: none; padding: .45rem .8rem; border-radius: 4px; font-weight: 500; opacity: .85; }
.barre__nav a:hover { background: var(--voile); opacity: 1; }
/* Une entrée mise en avant : celle qu'on veut qu'on remarque. Une seule à la fois —
   deux boutons en couleur n'attirent plus l'œil nulle part (v3.66). */
.barre__nav a.vedette { background: var(--accent); color: #fff; opacity: 1; font-weight: 600; }
.barre__nav a.vedette:hover { background: var(--accent); filter: brightness(1.12); }
/* Et elle bat, lentement (v3.69). Deux pulsations rapprochées puis un long repos : c'est le
   rythme d'un cœur au calme, et c'est ce qui fait qu'on le reconnaît comme un battement plutôt
   que comme un clignotement. Trois secondes de cycle, 6 % d'amplitude au plus — de loin on voit
   un mouvement, de près rien ne tremble sous le curseur.
   Le battement s'arrête dès qu'on survole ou qu'on tabule dessus (on l'a vue, elle a fait son
   travail), et sur la page elle-même : inutile d'appeler là où l'on est déjà. */
.barre__nav a.vedette { animation: coeur 3s ease-in-out infinite; transform-origin: center; }
.barre__nav a.vedette:hover,
.barre__nav a.vedette:focus-visible,
.barre__nav a.vedette[aria-current="page"] { animation: none; }
@keyframes coeur {
  0%          { transform: scale(1); }
  7%          { transform: scale(1.06); }
  14%         { transform: scale(1); }
  19%         { transform: scale(1.035); }
  25%, 100%   { transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) { .barre__nav a.vedette { animation: none; } }
/* Le décompte du projet, collé à son entrée : visible sans crier. */
.barre__compteur { display: inline-block; margin-left: .4rem; padding: .05rem .4rem; border-radius: 999px;
                   background: var(--voile); font-size: .76em; font-weight: 700; letter-spacing: .02em; }
/* Cinq entrées depuis l'arrivée du mémento : entre 720 et 960 px, elles ne tiennent plus à côté
   du nom et du bouton de déconnexion, et se tassaient les unes sur les autres. La barre de
   navigation prend sa propre ligne un peu plus tôt (v3.82). */
@media (max-width: 960px) {
  .barre { height: auto; flex-wrap: wrap; padding-top: .4rem; padding-bottom: .4rem; }
  .barre__nav { flex: 1 1 100%; flex-wrap: wrap; order: 3; }
}
.barre__user { display: flex; align-items: center; gap: .8rem; font-size: .9rem; }
/* « Nouveautés » : discret, mais avec une pastille quand il y a du neuf (v3.85) */
.barre__nouv { position: relative; color: var(--inverse-texte); text-decoration: none; opacity: .85;
               white-space: nowrap; padding: .3rem .1rem; }
.barre__nouv:hover { opacity: 1; text-decoration: underline; }
.barre__pastille { position: absolute; top: .1rem; right: -.45rem; width: .5rem; height: .5rem;
                   border-radius: 50%; background: var(--accent); }

/* La page des nouveautés */
.nouv-liste { list-style: none; margin: 0; padding: 0; }
.nouv { background: var(--carte); border: 1px solid var(--trait); border-left: 3px solid var(--accent);
        border-radius: var(--rayon); padding: .9rem 1.1rem; margin-bottom: .8rem; }
.nouv__tete { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.nouv__tete h2 { margin: 0; font-size: 1.1rem; }
.nouv__date { font-size: .8rem; color: var(--encre-douce); white-space: nowrap; }
.nouv p { margin: .4rem 0 0; line-height: 1.5; }
/* Sur écran étroit la barre se replie déjà sur plusieurs lignes (v3.82) : le bouton y a sa place,
   on se contente de le resserrer. */
@media (max-width: 960px) { .barre__nouv { font-size: .85rem; } }
.barre__nom { color: var(--mousse); }

/* ---------- Contenu ---------- */
.contenu { flex: 1; width: min(1200px, 94%); margin: 1.6rem auto; }
.contenu--etroit { width: min(560px, 94%); }
.carte { background: var(--carte); border: 1px solid var(--trait); border-radius: var(--rayon); box-shadow: var(--ombre); padding: 1.4rem 1.6rem; margin-bottom: 1.2rem; }
.grille { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
.grille > *, .grille--2 > * { min-width: 0; }   /* sinon un contenu large élargit la colonne (v3.46) */
.sous-titre { color: var(--encre-douce); margin-top: -.3rem; }
.muet { color: var(--encre-douce); font-size: .9rem; }

/* ---------- Boutons ---------- */
.btn {
  display: inline-block; border: none; border-radius: 4px; padding: .55rem 1.1rem;
  font: inherit; font-weight: 700; cursor: pointer; text-decoration: none;
  background: var(--accent); color: #fff; transition: background .15s;
}
.btn:hover { background: var(--accent-fonce); }
.btn--vert { background: var(--accent-2); } .btn--vert:hover { background: var(--accent-2-fonce); }
.btn--fantome { background: transparent; color: inherit; border: 1px solid var(--voile-trait); font-weight: 500; }
.btn--fantome:hover { background: var(--voile); border-color: var(--accent); }
.btn--secondaire { background: var(--papier-2); color: var(--encre); border: 1px solid var(--trait); font-weight: 500; }
.btn--secondaire:hover { background: var(--survol-2); }
.btn--petit { padding: .3rem .7rem; font-size: .85rem; }
.btn:disabled { opacity: .45; cursor: not-allowed; }

/* ---------- Formulaires ---------- */
label { display: block; font-weight: 700; margin: .8rem 0 .3rem; font-size: .92rem; }
input[type=text], input[type=password], input[type=email], input[type=number],
input[type=date], input[type=datetime-local], input[type=file], select, textarea {
  width: 100%; padding: .6rem .7rem; border: 1px solid var(--trait); border-radius: 4px;
  font: inherit; background: var(--carte); color: inherit;
}
input:focus-visible, select:focus-visible, textarea:focus-visible, .btn:focus-visible { outline: 3px solid rgba(194,90,31,.4); outline-offset: 1px; }
.aide { font-size: .84rem; color: var(--encre-douce); margin-top: .2rem; }
.ligne-champs { display: flex; gap: 1rem; flex-wrap: wrap; align-items: end; }
.ligne-champs > * { flex: 1 1 160px; }

/* La fiche d'un TP, écran « TP » : un en-tête, des blocs titrés, un pied (v3.43) */
.tp-fiche { padding: 1rem 1.4rem 1.2rem; }
.tp-fiche__entete { display: flex; align-items: flex-end; gap: 1rem;
                    padding-bottom: .9rem; border-bottom: 1px solid var(--trait); }
.tp-fiche__num { font-family: "Fraunces", Georgia, serif; font-size: 1.35rem; font-weight: 700;
                 white-space: nowrap; padding-bottom: .3rem; }
.tp-fiche__titre { flex: 1; min-width: 0; }
.tp-fiche__titre label { margin-top: 0; }
.tp-fiche__entete .btn { flex: 0 0 auto; }
.tp-bloc { margin-top: 1rem; }
.tp-bloc__titre { margin: 0 0 .1rem; font-size: .7rem; font-weight: 700; text-transform: uppercase;
                  letter-spacing: .08em; color: var(--encre-douce); }
/* Les cases à cocher d'un bloc : alignées entre elles, jamais calées à la main sur un champ voisin */
.tp-cases { display: flex; gap: .45rem 1.5rem; flex-wrap: wrap; align-items: flex-start; padding-bottom: .35rem; }
.tp-cases .case { display: flex; gap: .4rem; align-items: flex-start; font-weight: 400;
                  margin: .9rem 0 0; font-size: .92rem; line-height: 1.3; }
.tp-cases .case input { margin: .15rem 0 0; flex: 0 0 auto; }
.tp-fiche__enregistrer { margin: 1.1rem 0 0; }
.tp-fiche__pied { margin-top: 1.1rem; padding-top: .8rem; border-top: 1px solid var(--trait); }

/* ---------- Messages ---------- */
.flash { padding: .8rem 1rem; border-radius: 4px; margin-bottom: 1rem; border-left: 4px solid; }
.flash--succes { background: rgba(91,123,58,.1); border-color: var(--accent-2); }
.flash--erreur { background: rgba(179,38,30,.08); border-color: var(--danger); }
.flash--info { background: rgba(194,90,31,.08); border-color: var(--accent); }

/* ---------- Tableaux ---------- */
.tableau-conteneur { overflow: auto; max-height: calc(100vh - 230px); } /* défile dans sa boîte : l'en-tête collant reste correct */
table.donnees { width: 100%; border-collapse: collapse; font-size: .9rem; background: var(--carte); }
.donnees th, .donnees td { border: 1px solid var(--trait); padding: .45rem .6rem; text-align: left; vertical-align: middle; white-space: nowrap; }
.donnees th { background: var(--inverse-fond); color: var(--inverse-texte); font-size: .75rem; text-transform: uppercase; letter-spacing: .06em; position: sticky; top: 0; z-index: 2; }
.donnees th.tri { cursor: pointer; user-select: none; }
.donnees th.tri:hover, .donnees th.tri:focus-visible { background-image: linear-gradient(rgba(255,255,255,.12), rgba(255,255,255,.12)); }
.donnees th[aria-sort] { background-image: linear-gradient(rgba(255,255,255,.2), rgba(255,255,255,.2)); }
.tri-fleche { opacity: .45; font-weight: 400; }
.donnees th[aria-sort] .tri-fleche { opacity: 1; }
.donnees tbody tr:nth-child(even) td { background: var(--papier-2); }
.donnees tbody tr:hover td { background: var(--survol); }
.donnees td.centre, .donnees th.centre { text-align: center; }

/* ---------- Tableau de bord : les quatre premières colonnes restent en place (v3.81) ----------
   Douze TP plus la maquette dépassent l'écran : en défilant vers la droite on perdait de vue
   à QUI appartenait la ligne. Photo, nom, groupe de TD et groupe de TP sont donc collés à gauche.
   Le TD vient avec : il est entre le nom et le TP, et on ne peut pas figer des colonnes qui ne
   se touchent pas.
   Les largeurs sont mesurées en JavaScript et posées dans --col1/2/3 : les deviner en CSS
   casserait au moindre changement de taille de police ou de zoom.
   border-collapse masque la bordure d'une cellule collante pendant le défilement ; c'est une
   ombre portée qui marque la séparation, et elle a l'avantage de montrer qu'il y a du contenu
   caché dessous. */
.grille--figee thead th:nth-child(-n+4), .grille--figee tbody td:nth-child(-n+4) { position: sticky; z-index: 1; }
.grille--figee thead th:nth-child(1), .grille--figee tbody td:nth-child(1) { left: 0; }
.grille--figee thead th:nth-child(2), .grille--figee tbody td:nth-child(2) { left: var(--col1, 3.6rem); }
.grille--figee thead th:nth-child(3), .grille--figee tbody td:nth-child(3) { left: var(--col2, 14rem); }
.grille--figee thead th:nth-child(4), .grille--figee tbody td:nth-child(4) { left: var(--col3, 18rem); }
.grille--figee thead th:nth-child(-n+4) { z-index: 3; }   /* le croisement passe devant */
.grille--figee thead th:nth-child(4), .grille--figee tbody td:nth-child(4) { box-shadow: 6px 0 7px -6px rgba(31,34,26,.35); }
/* Les cellules collantes doivent être opaques, sinon le tableau défile par transparence dessous.
   Le zébrage et le survol posent déjà un fond ; il ne manque que les lignes impaires. */
.grille--figee tbody tr td:nth-child(-n+4) { background: var(--carte); }
.grille--figee tbody tr:nth-child(even) td:nth-child(-n+4) { background: var(--papier-2); }
.grille--figee tbody tr:hover td:nth-child(-n+4) { background: var(--survol); }

/* Le choix des colonnes affichées, au-dessus du tableau */
.grille-vues { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; margin: 0 0 .6rem; }
.grille-vues .muet { margin-right: .2rem; }
@media (max-width: 720px) {
  /* Sur téléphone la colonne figée mangerait la moitié de l'écran : on la libère. */
  .grille--figee thead th:nth-child(-n+4), .grille--figee tbody td:nth-child(-n+4) { position: static; box-shadow: none; }
}
.photo { width: 38px; height: 38px; object-fit: cover; border-radius: 4px; background: var(--papier-2); }

/* ---------- Le mémento (v3.81) ----------
   Des fiches courtes plutôt qu'un tableau : une balise, ce qu'elle sert, ses attributs, un
   exemple. Le tableau obligeait à lire en travers ; la fiche se lit d'un coup d'œil et se range
   en colonnes selon la largeur disponible. */
.mem-chercher { display: flex; align-items: end; gap: .6rem; flex-wrap: wrap; padding: 1rem 1.2rem; }
.mem-chercher label { margin: 0; }
.mem-chercher input { flex: 1 1 22rem; }
.mem-famille { margin: 1.4rem 0 .6rem; font-size: 1rem; color: var(--encre-douce);
               border-bottom: 1px solid var(--trait); padding-bottom: .3rem; }
.mem-grille { display: grid; grid-template-columns: repeat(auto-fill, minmax(21rem, 1fr)); gap: .8rem; }
/* La vignette est un lien vers la fiche : tout le bloc est cliquable, pas seulement le nom. */
.mem-entree { display: block; border: 1px solid var(--trait); border-radius: var(--rayon);
              padding: .7rem .9rem; background: var(--carte); color: inherit; text-decoration: none; }
.mem-entree:hover, .mem-entree:focus-visible { border-color: var(--accent); background: var(--survol-2); }
.mem-entree__tete { display: flex; align-items: baseline; justify-content: space-between; gap: .6rem; }
.mem-voir { display: block; margin-top: .4rem; font-size: .8rem; font-weight: 600; color: var(--accent); }
.mem-nom { font-size: 1rem; font-weight: 700; background: none; padding: 0; color: var(--accent); }
.mem-tp { font-size: .7rem; font-weight: 700; letter-spacing: .04em; color: var(--encre-douce);
          background: var(--papier-2); border-radius: 999px; padding: .1rem .5rem; white-space: nowrap; }
.mem-resume { display: block; margin: .35rem 0 .4rem; font-size: .92rem; line-height: 1.45; }
.mem-valeurs { margin: 0 0 .5rem; font-size: .84rem; color: var(--encre-douce); line-height: 1.45; }
.mem-exemple { margin: 0; background: #1d1f1a; color: #e8e4cc; border-radius: 4px;
               padding: .6rem .7rem; font-size: .78rem; line-height: 1.5; overflow-x: auto; }
.mem-exemple code { background: none; color: inherit; padding: 0; white-space: pre; }
.mem-resultats { list-style: none; margin: 0; padding: 0; }
.mem-resultats li { padding: .5rem 0; border-bottom: 1px solid var(--trait); }
.mem-resultats li:last-child { border-bottom: none; }
.mem-resultats p { margin: .2rem 0 0; font-size: .86rem; line-height: 1.45; }

/* La fiche d'une balise ou d'une propriété (v3.82) */
.fiche-mem { max-width: 54rem; }
.mem-nom--grand { font-size: 1.8rem; }
.fiche-mem h1 { margin-bottom: .2rem; }
.fiche-piege { background: var(--revoir-fond); border-left: 3px solid var(--alerte);
               padding: .6rem .8rem; border-radius: 0 4px 4px 0; margin: .8rem 0 0; }
.fiche-copier { display: flex; justify-content: flex-end; margin-bottom: -.4rem; position: relative; z-index: 1; }
.mem-exemple--grand { font-size: .84rem; padding: .9rem 1rem; }
.mem-liens { display: flex; flex-wrap: wrap; gap: .5rem; margin: 0; }
.mem-liens a { text-decoration: none; }
.mem-liens code { background: var(--papier-2); padding: .2rem .55rem; border-radius: 4px; }
.mem-liens a:hover code { background: var(--survol); }

/* ---------- Pastilles et états ---------- */
.pastille { display: inline-block; padding: .12em .5em; border-radius: 999px; font-size: .75rem; font-weight: 700; letter-spacing: .03em; }
.pastille--ok { background: rgba(91,123,58,.15); color: var(--ok-texte); }
.pastille--alerte { background: rgba(183,121,31,.15); color: var(--alerte-texte); }
.pastille--danger { background: rgba(179,38,30,.12); color: var(--danger-texte); }
.pastille--neutre { background: var(--papier-2); color: var(--encre-douce); }
/* Un message archivé, dépliable sur la fiche de l'étudiant (v3.23) */
.message-recu { border-bottom: 1px solid var(--trait); padding: .5rem 0; }
.message-recu:last-of-type { border-bottom: none; }
.message-recu > summary { cursor: pointer; }
.message-corps { white-space: pre-wrap; background: var(--papier-2); border: 1px solid var(--trait);
  border-radius: 4px; padding: .8rem 1rem; margin: .6rem 0 0; font-size: .86rem; overflow-x: auto; }

/* « Déjà contacté » : une trace discrète, qui ne crie pas comme une alerte (v3.22) */
.pastille--contacte { background: rgba(91,123,58,.12); color: var(--ok-texte); cursor: help; font-weight: 600; }
.etat { font-size: 1.05rem; text-decoration: none; }
.w3c-err { color: var(--danger); font-size: .8rem; margin-left: .2rem; }
.w3c-avert { color: var(--alerte); font-size: .8rem; margin-left: .2rem; }
.decroche td:first-child { border-left: 4px solid var(--danger); }

/* ---------- Liste des TP (étudiant) ---------- */
.tp-carte { display: flex; flex-direction: column; gap: .5rem; }
.tp-carte__num { font-family: "JetBrains Mono", monospace; font-size: .72rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--accent); }
.tp-carte__titre { font-family: "Fraunces", serif; font-size: 1.15rem; margin: 0; }
.tp-carte__meta { font-size: .85rem; color: var(--encre-douce); display: flex; gap: 1rem; flex-wrap: wrap; }
.jauge { height: 6px; background: var(--papier-2); border-radius: 3px; overflow: hidden; }
.jauge > span { display: block; height: 100%; background: var(--accent-2); }
.tp-carte__actions { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .3rem; }

/* Ce qui reste à reprendre dans un rendu — rien ne s'affiche quand tout va bien (v2.75) */
.tp-carte__revoir { border-left: 3px solid var(--alerte); background: var(--revoir-fond);
  border-radius: 0 4px 4px 0; padding: .55rem .75rem; margin: .2rem 0 .1rem; font-size: .85rem; }
.tp-carte__revoir .revoir-titre { margin: 0 0 .3rem; font-weight: 700; }
.tp-carte__revoir ul { margin: 0; padding-left: 1.1rem; }
.tp-carte__revoir li { margin: .2rem 0; }
.tp-carte__revoir code { font-size: .92em; }
.tp-carte__revoir p.muet { margin: .4rem 0 0; font-size: .92em; }

/* ---------- Connexion ---------- */
.connexion { margin-top: 3rem; }
.connexion h1 { text-align: center; }
.onglets { display: flex; gap: .3rem; margin-bottom: 1rem; }
.onglets a { flex: 1; text-align: center; padding: .5rem; border-radius: 4px; text-decoration: none; color: var(--encre-douce); background: var(--papier-2); font-weight: 500; }
.onglets a.actif { background: var(--inverse-fond); color: var(--inverse-texte); }

/* ---------- Pied ---------- */
.pied { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 1rem; padding: 1rem 1.4rem; font-size: .82rem; color: var(--encre-douce); border-top: 1px solid var(--trait); }

/* ---------- Filtres du tableau de bord ---------- */
.filtres { display: flex; gap: .8rem; flex-wrap: wrap; align-items: end; margin-bottom: 1rem; }
.filtres > div { flex: 1 1 150px; }
.filtres label { margin-top: 0; }
.indicateurs { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1.2rem; }
.indicateur { flex: 1 1 130px; background: var(--carte); border: 1px solid var(--trait); border-radius: var(--rayon); padding: .9rem 1rem; box-shadow: var(--ombre); }
.indicateur strong { display: block; font-family: "Fraunces", serif; font-size: 1.6rem; line-height: 1.1; }
.indicateur span { font-size: .8rem; color: var(--encre-douce); text-transform: uppercase; letter-spacing: .06em; }
.indicateur--danger strong { color: var(--danger); }
.indicateur strong small { font-size: .62em; font-weight: 400; }

@media (max-width: 720px) {
  .barre { gap: .8rem; padding: 0 .8rem; height: auto; flex-wrap: wrap; padding-block: .5rem; }
  .barre__nav { flex: 1 1 100%; flex-wrap: wrap; order: 3; }   /* les liens passent à la ligne au lieu de faire déborder la page */
  .barre__nav a { padding: .35rem .5rem; font-size: .9rem; }
  .donnees th { position: static; }
}

/* ---------- Statistiques et fiche étudiant (v2.0) ---------- */
.fiche-entete { display: flex; gap: 1rem; align-items: center; margin-bottom: 1rem; }
.photo--grande { width: 72px; height: 72px; border-radius: 50%; object-fit: cover; }
.grille--2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 1rem; }
.grille-heures { border-collapse: separate; border-spacing: 2px; font-size: .72rem; }
.grille-heures th { font-weight: 600; color: var(--encre-douce); padding: 0 .2rem; text-align: center; position: static; background: none; }
.grille-heures td { width: 1.85em; height: 1.7em; text-align: center; border-radius: 3px; background: rgba(91,123,58, var(--i, 0)); color: #fff; font-size: .66rem; }
/* Heures nocturnes (22 h – 5 h) : fond légèrement plus sombre pour repérer le travail de nuit */
.grille-heures td.nuit[style="--i:0.00"] { background: var(--nuit); }
.grille-heures th { white-space: nowrap; }
.grille-heures td[style="--i:0.00"] { background: var(--papier-2); }
.barres-jours { display: flex; align-items: flex-end; gap: 2px; height: 120px; border-bottom: 1px solid var(--trait); }
.barres-jours span { flex: 1; height: var(--h, 0%); min-height: 1px; background: var(--accent-2); border-radius: 2px 2px 0 0; }
.barres-jours span.we { background: rgba(91,123,58,.45); }
.pages-barres { display: flex; align-items: flex-end; gap: 1px; height: 28px; min-width: 120px; }
.pages-barres span { flex: 1; height: var(--h, 0%); min-height: 2px; background: var(--accent); border-radius: 1px; }
.ligne--alerte td { background: var(--alerte-ligne); }

/* TP sans énoncé encore rédigé : carte grisée, non cliquable */
/* La frise du semestre : treize cases nommées, chacune avec sa part faite (v3.38) */
.frise { padding: 1.1rem 1.3rem; }
.frise__tete { display: flex; align-items: baseline; gap: .2rem 1rem; flex-wrap: wrap; margin-bottom: .9rem; }
.frise__pct { margin: 0; color: var(--encre-douce); font-size: .9rem; }
.frise__pct strong { font-family: "Fraunces", Georgia, serif; font-size: 1.6rem; color: var(--accent-2); }
.frise__etat { margin: 0; font-size: .9rem; }
/* La marge du bas réserve la place de l'étiquette « vous êtes ici », qui est en absolu */
.frise__liste { display: flex; gap: .3rem; list-style: none; margin: 0 0 1.4rem; padding: 0; }
.frise__case { flex: 1 1 0; min-width: 0; position: relative; }
.frise__lien { display: block; text-decoration: none; color: var(--encre-douce); }
a.frise__lien:hover .frise__nom { color: var(--accent); }
.frise__nom { display: block; font-size: .68rem; letter-spacing: .02em; text-align: center;
              white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: .25rem; }
.frise__barre { display: block; height: 8px; border-radius: 2px; background: var(--papier-2);
                border: 1px solid var(--trait); overflow: hidden; }
.frise__barre > span { display: block; height: 100%; background: var(--accent-2); }
.frise__case--encours .frise__barre > span { background: var(--alerte); }
/* Les deux pastilles de la phrase d'explication, sous la frise */
.frise__legende { display: inline-block; width: .7rem; height: .7rem; border-radius: 2px;
                  vertical-align: -1px; border: 1px solid var(--trait); }
.frise__legende--fait { background: var(--accent-2); }
.frise__legende--encours { background: var(--alerte); }
.frise__case--avenir .frise__lien { opacity: .45; }
.frise__case--avenir .frise__barre { background: repeating-linear-gradient(45deg,
    var(--papier-2) 0 3px, transparent 3px 6px); }
/* La maquette n'est pas un TP : elle se repère à sa couleur, et son nom demande un peu plus de place */
.frise__case--maquette { flex-grow: 1.5; }
.frise__case--maquette .frise__nom { color: var(--accent-2); }
.frise__case--ici .frise__nom { color: var(--encre); font-weight: 700; }
.frise__case--ici .frise__barre { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.frise__ici { position: absolute; left: 50%; transform: translateX(-50%); white-space: nowrap;
              margin-top: .3rem; font-size: .62rem; text-transform: uppercase; letter-spacing: .06em;
              color: var(--accent); }
.frise__nom--court { display: none; }
@media (max-width: 700px) {
  /* Treize noms ne tiennent pas sur un téléphone : le numéro seul suffit, le titre reste en infobulle */
  .frise__nom--long { display: none; }
  .frise__nom--court { display: inline; }
  .frise__nom { font-size: .68rem; }
  .frise__ici { font-size: .58rem; }
}

/* ---------- La montre à aiguilles, en tête de « Mes TP » (v3.72) ----------
   Elle était dans la barre : 36 px perdus au milieu des boutons, personne ne la voyait. Ici elle
   a la taille d'un vrai cadran, en face du titre, au-dessus de la même heure écrite en chiffres.
   Les trois aiguilles sont la même animation — un tour complet — à trois vitesses, lancée avec un
   retard négatif calculé au rendu. Aucune minuterie, rien à rafraîchir, et la trotteuse balaie
   au lieu de sauter, comme une mécanique. inc/montre.php écrit le cadran, et il donne toujours
   l'heure du SERVEUR : celle qui fait foi pour les dates limites. */
.accueil-entete { display: flex; align-items: flex-start; justify-content: space-between;
                  gap: 1.5rem 2rem; flex-wrap: wrap; }
.accueil-entete__texte { flex: 1 1 22rem; min-width: 0; }
.accueil-entete h1 { margin-top: 0; }
/* Deux tailles, un seul dessin. Les couleurs passent par des variables pour que le creux de
   l'aiguille des heures soit TOUJOURS exactement celle du cadran.
   Elles sont fixes, et ne suivent pas le thème : une montre est un objet, pas un élément
   d'interface — comme les aperçus des énoncés, elle garde le même aspect en clair et en sombre.
   Sans cela, le cadran noir deviendrait un disque blanc en thème sombre. */
.montre { --cadran: #15170F; --lunette: rgba(255,255,255,.32); --aiguilles: #EFEDE4;
          width: 36px; height: 36px; flex: none; line-height: 0; }
.montre svg { width: 100%; height: 100%; display: block; overflow: visible; }
.montre--grande { --lunette: #57594B; width: 124px; height: 124px; margin: .2rem .4rem 0 0; }
.montre__cadran, .montre__evide { fill: var(--cadran); }   /* le creux des aiguilles = le cadran */
.montre__lunette { fill: none; stroke: var(--lunette); stroke-width: 4; }
.montre__index     { fill: var(--aiguilles); opacity: .7; }
.montre__aiguille  { fill: var(--aiguilles); }
.montre__trotteuse { fill: var(--accent); }
.montre--grande svg { filter: drop-shadow(0 4px 10px rgba(31,34,26,.25)); }
.montre__h, .montre__m, .montre__s {
  transform-box: view-box; transform-origin: 50px 50px;
  animation: tour-montre linear infinite;
}
.montre__h { animation-duration: 43200s; }   /* 12 h */
.montre__m { animation-duration: 3600s; }    /*  1 h */
.montre__s { animation-duration: 60s; }      /*  1 min */
@keyframes tour-montre { to { transform: rotate(360deg); } }
/* Mouvement réduit : la trotteuse saute la seconde, comme un quartz, au lieu de balayer.
   L'heure reste lisible — c'est le balayage continu que l'on retire, pas l'information. */
@media (prefers-reduced-motion: reduce) {
  .montre__s { animation-timing-function: steps(60, end); }
}
@media (max-width: 720px) {
  /* Sur téléphone le titre prend toute la largeur : la montre passe dessous, plus petite.
     Et celle de la barre se resserre avec le reste. */
  .montre--grande { width: 84px; height: 84px; margin-top: 0; }
  .barre__user .montre { width: 30px; height: 30px; }
}

/* L'heure du serveur et le point d'avancement, en tête de « Mes TP » (v3.36) */
.horloge { display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap;
           margin: -.4rem 0 1.1rem; font-size: .9rem; color: var(--encre-douce); }
.horloge__jour { text-transform: none; }
.horloge__heure { font-family: "JetBrains Mono", ui-monospace, monospace; font-weight: 700;
                  color: var(--encre); font-variant-numeric: tabular-nums; }
.alerte-etu { border-left: 3px solid var(--alerte); background: var(--revoir-fond); position: relative; }
/* Le × d'un encadré qu'on peut refermer : discret, mais assez grand pour le doigt (v3.66) */
.carte-fermer { position: absolute; top: .4rem; right: .5rem; width: 2rem; height: 2rem; line-height: 1;
                border: 0; border-radius: 50%; background: transparent; color: var(--encre-douce);
                font-size: 1.4rem; cursor: pointer; }
.carte-fermer:hover { background: var(--voile); color: var(--encre); }
.alerte-etu__rappel { margin: -.4rem 0 1.1rem; }
.lien-nu { border: 0; background: none; padding: 0; font: inherit; font-size: .9rem;
           color: var(--accent); cursor: pointer; text-decoration: underline; }
.alerte-etu h2 { font-size: 1.1rem; }
.alerte-etu__liste { margin: .8rem 0; padding-left: 1.1rem; }
.alerte-etu__liste li { margin: .5rem 0; }
.tp-carte--projet { border-left: 3px solid var(--accent-2); }
.tp-carte--projet .tp-carte__num { color: var(--accent-2); }
.tp-carte--grise { opacity: .55; filter: grayscale(.6); background: var(--papier-2); }
.tp-carte--grise .tp-carte__titre { color: var(--encre-douce); }
/* Un TP pas encore ouvert : le titre seul, sans le relief d'une carte où l'on peut entrer */
.tp-carte--avenir { box-shadow: none; filter: none; opacity: .62; border-style: dashed; }

/* Confirmation affichée dans la page (remplace window.confirm, que le navigateur peut bloquer) */
.confirmation { display: block; margin: .5rem 0; padding: .7rem .9rem; background: var(--confirmation-fond); border: 1px solid var(--alerte);
  border-left-width: 4px; border-radius: 6px; max-width: 34rem; white-space: normal; text-align: left; font-size: .9rem; }
.confirmation p { margin: 0 0 .5rem; color: var(--encre, #1f221a); }
.confirmation__actions { display: flex; gap: .5rem; flex-wrap: wrap; }

/* Action irréversible (suppression) */
.btn--danger { background: var(--danger, #b3261e); color: #fff; border-color: var(--danger, #b3261e); }
.btn--danger:hover { filter: brightness(.92); }

/* Avatar : photo Moodle si disponible, sinon initiales sur une pastille de couleur stable par étudiant
   (luminosité 34 % : texte blanc lisible quelle que soit la teinte) */
.avatar { position: relative; display: inline-grid; place-items: center; width: 38px; height: 38px; border-radius: 50%;
  overflow: hidden; background: hsl(var(--teinte, 90) 32% 34%); color: #fff; font-weight: 700; font-size: .8rem;
  letter-spacing: .03em; vertical-align: middle; flex-shrink: 0; user-select: none; }
.avatar img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0; transition: opacity .2s; }
.avatar img.chargee { opacity: 1; }   /* la photo ne masque les initiales qu'une fois réellement chargée */
.avatar--grand { width: 72px; height: 72px; font-size: 1.45rem; }

/* Effectifs par groupe (tableau de bord) : une ligne par groupe ; un clic filtre le tableau, un second retire le filtre */
.groupes-effectifs { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 1rem 2.5rem; padding: 1rem 1.2rem .8rem; margin-bottom: 1rem; }
.groupes-effectifs h2 { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; font-size: 1.05rem;
  margin: 0 0 .2rem; padding-bottom: .4rem; border-bottom: 2px solid var(--encre); }
.groupes-effectifs h2 span { font-family: "DM Sans", system-ui, sans-serif; font-size: .85rem; font-weight: 500; color: var(--encre-douce); }
.groupes-liste { list-style: none; margin: 0; padding: 0; }
.groupes-liste li + li { border-top: 1px solid var(--trait); }
.groupe { display: grid; grid-template-columns: 3.6rem 8.6rem minmax(30px, 1fr) 4.4rem; align-items: center; gap: .7rem;
  padding: .5rem .6rem; color: var(--encre); text-decoration: none; border-radius: 4px; }
a.groupe:hover { background: var(--papier-2); }
a.groupe:focus-visible { outline: 3px solid var(--accent-2); outline-offset: -3px; }
.groupe__code { font-family: "Fraunces", Georgia, serif; font-weight: 700; font-size: 1.05rem; white-space: nowrap; }
.groupe__creneau { font-size: .88rem; color: var(--encre-douce); white-space: nowrap; }
.groupe__jauge { height: 8px; background: var(--papier-2); border-radius: 4px; overflow: hidden; }
.groupe__jauge > span { display: block; height: 100%; background: var(--accent-2); border-radius: 4px; }
.groupe__jauge--vide { background: none; }
.groupe__n { text-align: right; font-family: "Fraunces", Georgia, serif; font-weight: 700; font-size: 1.2rem; font-variant-numeric: tabular-nums; white-space: nowrap; }
.groupe__n small { font-family: "DM Sans", system-ui, sans-serif; font-size: .75rem; font-weight: 400; color: var(--encre-douce); }
.groupe--actif { background: var(--groupe-actif); box-shadow: inset 4px 0 0 var(--accent-2); }
.groupe--sans .groupe__code { grid-column: span 2; font-family: "DM Sans", system-ui, sans-serif; font-size: .9rem; color: var(--encre-douce); }
.groupe--sans .groupe__code small { font-weight: 400; }
.groupe--sans .groupe__n { color: var(--encre-douce); }
@media (max-width: 480px) {
  .groupes-effectifs { grid-template-columns: 1fr; }
  .groupe { grid-template-columns: 3.6rem minmax(0, 1fr) 4.4rem; }
  .groupe__jauge { display: none; }
}

/* Statistiques : tableaux par TP (le titre du TP peut passer à la ligne, sinon la dernière colonne sort de l'écran) */
.donnees--tp th, .donnees--tp td { white-space: normal; }
/* Un tableau de phrases, pas de chiffres : le texte s'y replie au lieu d'élargir la carte (v3.46) */
.donnees--souple td { white-space: normal; }
.donnees--tp td:first-child { max-width: 20rem; line-height: 1.35; }

/* Statistiques : « qui est dans quel cas ? » sous le tableau des rendus */
.qui-rendus { margin-top: .6rem; border-top: 1px solid var(--trait); padding-top: .5rem; }
.qui-rendus > summary { cursor: pointer; font-weight: 600; }
.qui-rendus > p { margin: .45rem 0 0; line-height: 1.6; }
/* Sous-lignes « groupe de TP » : plus discrètes que la ligne du TP, et sans le zébrage des lignes normales
   (sélecteur aussi précis que .donnees tbody tr:nth-child(even) td, sinon le zébrage l'emporte) */
.donnees tbody tr.ligne-groupe td { background: var(--carte); font-size: .88em; color: var(--encre-douce); }
.donnees tbody tr.ligne-groupe td:first-child { padding-left: 1.7rem; }
.donnees tbody tr.ligne-tp td { background: var(--papier-2); border-top: 2px solid var(--olive); }
.corrige-lu { font-size: .8em; opacity: .75; }
a.indicateur { color: inherit; }
a.indicateur:hover { border-color: var(--accent); }
.prevenir { font-size: .78rem; color: var(--encre-douce); white-space: nowrap; margin-right: .2rem; }

/* Bouton « copier » devant une valeur à recopier (v2.77) */
.copier { position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 1.5rem; height: 1.5rem; padding: 0; margin-right: .25rem; vertical-align: -.25rem;
  border: 1px solid var(--trait); border-radius: 4px; background: var(--papier-2, #F5EFD3);
  color: var(--encre-douce); cursor: pointer; }
.copier:hover { border-color: var(--accent); color: var(--accent); }
.copier svg { width: .85rem; height: .85rem; pointer-events: none; }
.copier__etat { position: absolute; left: 50%; bottom: calc(100% + .35rem); transform: translateX(-50%);
  background: var(--inverse-fond); color: var(--inverse-texte); font-size: .72rem; line-height: 1; white-space: nowrap;
  padding: .3rem .45rem; border-radius: 3px; pointer-events: none; }
.copier__etat--echec { background: var(--danger); }


/* =============================================================================
   Mode sombre (v3.16)
   -----------------------------------------------------------------------------
   Une seule palette à tenir : le thème est porté par data-theme sur <html>, posé
   par le petit script de l'en-tête AVANT le premier rendu — il lit le choix
   mémorisé, et à défaut le réglage du système. Rien d'autre ne change : chaque
   règle de cette feuille continue de lire les mêmes variables.

   Ce qui reste volontairement clair dans les deux thèmes : les aperçus de
   navigateur et les schémas des énoncés. Ce sont des images de ce que l'étudiant
   verra dans SON navigateur, fond blanc compris ; les repeindre en sombre
   montrerait autre chose que la réalité.
   ============================================================================= */
:root[data-theme="dark"] {
  color-scheme: dark;
  --papier: #15171B; --papier-2: #1F2228; --encre: #E9E6DB; --encre-douce: #AEACA1;
  --olive: #A3A592; --mousse: #B9BCA8; --trait: #343842;
  --accent: #E2823F; --accent-2: #8FB364; --danger: #EF8178; --alerte: #DFA752;
  --ombre: 0 1px 0 rgba(0,0,0,.35), 0 6px 24px -12px rgba(0,0,0,.65);
  --carte: #1A1D22; --survol: #272B33; --survol-2: #2B2F38;
  --accent-fonce: #C96B2B; --accent-2-fonce: #76994E;
  --inverse-fond: #23272E; --inverse-texte: #EFEDE4;
  --ok-texte: #A8C98A; --alerte-texte: #E3B467; --danger-texte: #F19A93;
  --nuit: #242830; --confirmation-fond: #2A241B; --groupe-actif: #232A22;
  --voile: rgba(255,255,255,.08); --voile-trait: rgba(255,255,255,.28);
  --code-ligne: rgba(255,255,255,.09);
  --revoir-fond: rgba(224,166,74,.10);
  --alerte-ligne: rgba(239,129,122,.10);
}

/* Qui travaille en ce moment (v3.17) : un point vert qui bat doucement, dans l'indicateur du
   haut et devant la dernière activité de chaque étudiant. Le battement s'arrête pour qui préfère
   les animations réduites. */
.point-direct { display: inline-block; width: .55rem; height: .55rem; border-radius: 50%;
  background: var(--accent-2); margin-right: .4rem; vertical-align: .05em;
  box-shadow: 0 0 0 0 rgba(91,123,58,.5); animation: battement 2.4s ease-out infinite; }
.point-direct--eteint { background: var(--trait); animation: none; box-shadow: none; }
@keyframes battement {
  0%   { box-shadow: 0 0 0 0 rgba(91,123,58,.45); }
  70%  { box-shadow: 0 0 0 .5rem rgba(91,123,58,0); }
  100% { box-shadow: 0 0 0 0 rgba(91,123,58,0); }
}
@media (prefers-reduced-motion: reduce) { .point-direct { animation: none; } }
/* L'indicateur est un bouton : un clic déplie la liste de ceux qui sont là */
.indicateur--direct { cursor: pointer; font: inherit; text-align: left; color: inherit; }
.indicateur--direct:hover { border-color: var(--accent); }
.indicateur--direct[aria-expanded="true"] { border-color: var(--accent-2); box-shadow: inset 3px 0 0 var(--accent-2); }

/* Connexions par jour, avec la barre du jour en direct (v3.27) */
.graphe-cx { padding: 1rem 1.2rem .8rem; }
.graphe-cx__titre { display: flex; justify-content: space-between; align-items: baseline; flex-wrap: wrap; gap: .5rem; }
.graphe-cx__titre h2 { margin: 0 0 .2rem; }
.graphe-cx__titre p { margin: 0; }
.graphe-cx__barres { display: flex; align-items: flex-end; gap: .35rem; margin: .8rem 0 0; }
/* Une colonne = trois rangées de hauteur fixe : le chiffre, la PISTE où vit la barre, la date.
   Une grille plutôt qu'une pile souple, pour que la piste mesure exactement la même chose dans
   toutes les colonnes — y compris celles dont la date est vide ou masquée (v3.67). */
.graphe-cx__jour { flex: 1 1 0; display: grid; grid-template-rows: .9rem 1fr .9rem; gap: .2rem;
  text-align: center; min-width: 0; cursor: help; }
/* La colonne garde sa hauteur même quand la barre est nulle : les jours vides restent lisibles.
   La barre est posée dans une PISTE : son pourcentage se mesure sur la hauteur libre, une fois le
   chiffre et la date retirés. Avant (v3.66), il se mesurait sur toute la colonne : les barres les
   plus hautes débordaient, le navigateur les comprimait, et 40 et 48 finissaient à la même
   hauteur — l'échelle était juste en bas, plate au-dessus de 70 % (corrigé en v3.67). */
.graphe-cx__piste { position: relative; min-height: 0; width: 100%; }
.graphe-cx__barre { position: absolute; bottom: 0; left: 50%; transform: translateX(-50%);
  width: 100%; max-width: 2.2rem; height: var(--h, 0%);
  min-height: 2px; background: var(--accent-2); border-radius: 3px 3px 0 0; transition: height .4s ease; }
.graphe-cx__jour { height: 8.5rem; }
.graphe-cx__valeur { font-size: .7rem; color: var(--encre-douce); font-variant-numeric: tabular-nums; }
.graphe-cx__date { font-size: .64rem; color: var(--encre-douce); white-space: nowrap; }
.graphe-cx__jour--we .graphe-cx__barre { background: rgba(91,123,58,.35); }
.graphe-cx__jour--aujourdhui .graphe-cx__barre { background: var(--accent); }
.graphe-cx__jour--aujourdhui .graphe-cx__date { color: var(--accent); font-weight: 700; }
@media (max-width: 720px) {
  .graphe-cx__date { display: none; }
  .graphe-cx__jour--aujourdhui .graphe-cx__date { display: block; }
}

/* Bandeau du mode observation : impossible à manquer, sur toutes les pages (v3.35) */
.observation-bandeau { position: sticky; top: 54px; z-index: 45; display: flex; align-items: center;
  justify-content: space-between; gap: 1rem; flex-wrap: wrap;
  background: var(--alerte); color: #fff; padding: .55rem 1.2rem; font-size: .9rem; }
.observation-bandeau code { background: rgba(255,255,255,.2); color: #fff; }
.observation-bandeau .btn { background: #fff; color: var(--encre); }
.observation-bandeau .btn:hover { background: var(--papier-2); }
@media (max-width: 720px) { .observation-bandeau { position: static; } }

/* Lettre ECTS : un classement, pas un niveau — d'où un dégradé plutôt que vert/rouge (v3.33) */
.ects { display: inline-grid; place-items: center; width: 1.7rem; height: 1.7rem; border-radius: 4px;
  font-family: "Fraunces", Georgia, serif; font-weight: 700; font-size: .95rem; color: #fff; }
.ects--A { background: #2f6b3a; }
.ects--B { background: #5b8f4a; }
.ects--C { background: #8a9a52; }
.ects--D { background: #c08a3e; }
.ects--E { background: #b1663a; }
.ects-barre { display: block; height: .5rem; border-radius: 3px; background: var(--papier-2); }
.ects-barre > span { display: block; height: 100%; width: var(--p, 0%); border-radius: 3px; background: var(--accent-2); }

/* Notes : chaque note est un bouton qui ouvre son calcul (v3.32) */
.note { border: 1px solid var(--trait); border-radius: 4px; background: var(--carte); color: inherit;
  font: inherit; font-weight: 700; font-variant-numeric: tabular-nums; padding: .15rem .45rem; cursor: pointer; }
.note:hover { border-color: var(--accent); }
.note--sur  { background: rgba(91,123,58,.14); color: var(--ok-texte); border-color: transparent; }
.note--sous { background: rgba(183,121,31,.14); color: var(--alerte-texte); border-color: transparent; }
.note--moyenne { border-style: dashed; font-weight: 700; }
.note-detail { position: sticky; bottom: 0; background: var(--carte); border: 1px solid var(--trait);
  border-radius: var(--rayon); box-shadow: var(--ombre); padding: 1rem 1.2rem; margin-top: 1rem; }
.note-detail table { margin: .6rem 0 .4rem; }

/* Choix de la période et détail d'une journée (v3.30) */
.graphe-cx__periodes { display: flex; gap: .35rem; flex-wrap: wrap; margin: .6rem 0 0; }
/* Les barres sont des boutons : on neutralise l'apparence, on garde le clic et le focus */
.graphe-cx__jour { border: 0; background: none; padding: 0; font: inherit; cursor: pointer; }
.graphe-cx__jour:hover .graphe-cx__barre { filter: brightness(1.15); }
.graphe-cx__jour:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
.graphe-cx__jour--ouvert .graphe-cx__barre { box-shadow: 0 0 0 2px var(--accent); }
.graphe-cx__barres--dense { gap: .12rem; }
.graphe-cx__barres--dense .graphe-cx__jour { height: 6.8rem; }
.graphe-cx__barres--dense .graphe-cx__date { font-size: .58rem; }

/* Liens cassés d'un rendu (v3.68) : une adresse peut être très longue, et c'est la RAISON qui
   doit rester lisible — l'adresse se coupe donc où il faut plutôt que de pousser la colonne. */
.liens-table { table-layout: fixed; width: 100%; }
.liens-table td:nth-child(3) { width: 45%; }
.liens-table td:nth-child(4) { width: 35%; }
.liens-table td { white-space: normal; }   /* .donnees impose nowrap : ici le texte doit revenir à la ligne */
.liens-table code { word-break: break-all; white-space: normal; font-size: .8rem; }

/* Le code déposé par un étudiant : la page HTML, puis la feuille de style à partir du TP 6.
   Le style était écrit à la main dans la balise ; il sert maintenant à deux blocs (v3.83). */
.code-depose { background: #1d1f1a; color: #e8e4cc; border-radius: 4px; padding: 1rem;
               font-size: .82rem; line-height: 1.5; overflow: auto; max-height: 32rem; }

/* Coloration syntaxique (v3.84). Les couleurs sont FIXES, comme celles de la montre : un bloc de
   code est un objet qu'on lit, pas un élément d'interface — il garde le même aspect en clair et
   en sombre, et c'est aussi ce qui permet de le reconnaître d'un coup d'œil. */
.tok-balise { color: #9cc27a; }                    /* le nom d'une balise, et le sélecteur */
.tok-sel    { color: #9cc27a; font-weight: 600; }
.tok-attr   { color: #e8c07d; }                    /* un attribut, et une propriété */
.tok-prop   { color: #e8c07d; }
.tok-valeur { color: #f3b98f; }                    /* ce qu'on leur donne */
.tok-nombre { color: #d8a0df; }
.tok-comm   { color: #8a8f7d; font-style: italic; }
.tok-meta   { color: #7fb3d5; }                    /* doctype, @media */
.tok-ponct  { color: #b9bca8; }

.jour-detail { margin-top: 1rem; padding-top: .9rem; border-top: 1px solid var(--trait); }
.jour-detail__entete { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; }
.jour-detail__entete h3 { margin: 0; }
/* Les vingt-quatre heures de la journée : une colonne par heure, les heures creuses à plat */
.jour-heures { display: flex; align-items: flex-end; gap: 2px; height: 4.5rem; margin: .7rem 0 .2rem; }
/* Même grille que le graphique du haut : la piste d'abord, l'étiquette ensuite, de hauteur fixe —
   une heure sur trois seulement est nommée, et les autres doivent garder la même piste (v3.67). */
.jour-heures > span { flex: 1 1 0; display: grid; grid-template-rows: 1fr .9rem; gap: .15rem;
  text-align: center; height: 100%; font-size: .58rem; color: var(--encre-douce); }
/* Même piste que le graphique du haut, et pour la même raison : sans elle, les heures les plus
   chargées se retrouvaient toutes à la même hauteur (v3.67). */
.jour-heures__piste { position: relative; min-height: 0; width: 100%; }
.jour-heures b { position: absolute; bottom: 0; left: 50%; transform: translateX(-50%);
  width: 100%; max-width: 1.4rem; height: var(--h, 0%); min-height: 2px;
  background: var(--accent-2); border-radius: 2px 2px 0 0; }
.jour-heures .seance b { background: var(--accent); }
.jour-liste { list-style: none; margin: .8rem 0 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: .1rem .9rem; }
.jour-liste li { padding: .22rem 0; border-bottom: 1px solid var(--trait); font-size: .88rem; }
.jour-liste li:last-child { border-bottom: none; }
.jour-liste time { font-family: "JetBrains Mono", monospace; font-size: .8rem; color: var(--encre-douce); margin-right: .4rem; }

/* Barre d'action au-dessus d'une liste à cocher (écrire aux sélectionnés, v3.21) */
.barre-selection { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
  padding: .6rem .9rem; margin-bottom: .6rem; }
.barre-selection label { display: flex; align-items: center; gap: .4rem; }
.barre-selection button { margin-left: auto; }

/* La liste dépliée : une ligne par étudiant, en colonnes sur grand écran */
.en-direct-liste { padding: .9rem 1.2rem; }
.en-direct-titre { display: flex; align-items: center; flex-wrap: wrap; gap: .5rem; margin: 0 0 .6rem;
  padding-bottom: .5rem; border-bottom: 1px solid var(--trait); font-size: .95rem; }
.en-direct-titre strong { font-family: "Fraunces", Georgia, serif; font-size: 1.15rem; }
/* Le bandeau LIVE, comme sur une diffusion en direct : ce qui suit vaut pour maintenant */
.badge-live { display: inline-flex; align-items: center; background: var(--accent-2); color: #fff;
  font-family: "JetBrains Mono", ui-monospace, monospace; font-size: .68rem; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase; padding: .22rem .5rem .22rem .45rem; border-radius: 3px; }
.badge-live .point-direct { background: #fff; box-shadow: 0 0 0 0 rgba(255,255,255,.6); margin-right: .35rem;
  width: .45rem; height: .45rem; animation-name: battement-clair; }
@keyframes battement-clair {
  0%   { box-shadow: 0 0 0 0 rgba(255,255,255,.55); }
  70%  { box-shadow: 0 0 0 .35rem rgba(255,255,255,0); }
  100% { box-shadow: 0 0 0 0 rgba(255,255,255,0); }
}
.en-direct-items { list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); gap: .15rem 1.5rem; }
.en-direct-items li { padding: .3rem 0; border-bottom: 1px solid var(--trait); font-size: .92rem; }
.en-direct-items li:last-child { border-bottom: none; }
.en-direct-items .muet { font-size: .82rem; }
.en-direct-vide { margin: 0; }
/* Texte réservé aux lecteurs d'écran : hors de l'écran, mais lu à voix haute */
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* Bouton clair / sombre, à droite de la barre supérieure. Les deux icônes sont dans le
   bouton ; le thème en cache une, si bien que rien ne clignote au chargement. */
.theme-btn__clair { display: none; }
:root[data-theme="dark"] .theme-btn__clair { display: block; }
:root[data-theme="dark"] .theme-btn__sombre { display: none; }
/* Bouton clair / sombre, à droite de la barre supérieure */
.theme-btn {
  display: inline-grid; place-items: center; width: 2rem; height: 2rem; padding: 0;
  border: 1px solid var(--voile-trait); border-radius: 4px; background: transparent;
  color: inherit; cursor: pointer; font-size: .95rem; line-height: 1; flex-shrink: 0;
}
.theme-btn:hover { background: var(--voile); border-color: var(--accent); }
.theme-btn:focus-visible { outline: 3px solid var(--accent); outline-offset: 1px; }

/* ─────────────────────────────────────────────────────────────────────────────
   Galerie des maquettes (v3.64) : la séance d'affiches de fin de semestre, en ligne.
   L'aperçu d'une maquette est la vraie page, chargée dans un cadre large et réduite
   à l'échelle de la carte. Comme les aperçus des énoncés, ces vues restent sur fond
   clair dans les deux thèmes : ce sont des documents, pas de l'interface.
   ───────────────────────────────────────────────────────────────────────────── */
.maquettes-attente { text-align: center; }
.maquettes-attente h2 { margin-top: 0; }
.compte-rebours { display: flex; justify-content: center; gap: .9rem; flex-wrap: wrap; margin: 1.2rem 0 .6rem; }
.compte-rebours > div { min-width: 76px; padding: .6rem .4rem; border: 1px solid var(--trait);
                        border-radius: 8px; background: var(--papier); }
.compte-rebours strong { display: block; font-size: 1.9rem; line-height: 1.1; font-variant-numeric: tabular-nums; }
.compte-rebours span { font-size: .72rem; text-transform: uppercase; letter-spacing: .08em; color: var(--olive); }
.maquettes-date { margin-top: .4rem; }
.projet-echeance { text-align: center; }
.projet-echeance h2 { margin-top: 0; }
.projet-echeance .compte-rebours strong { font-size: 2.4rem; }
.projet-date { margin: .2rem 0 0; }

.maquettes-filtres { display: flex; align-items: center; gap: 1.2rem; flex-wrap: wrap; padding: .7rem .9rem; }
.maquettes-filtre { display: flex; align-items: center; gap: .35rem; flex-wrap: wrap; }
.maquettes-filtre__label { font-size: .72rem; text-transform: uppercase; letter-spacing: .08em; color: var(--olive); margin-right: .2rem; }
.pilule { padding: .25rem .8rem; font: inherit; font-size: .85rem; border-radius: 999px;
          border: 1px solid var(--trait); background: transparent; color: var(--encre);
          cursor: pointer; transition: background .15s, border-color .15s; }
.pilule:hover { border-color: var(--olive); }
.pilule.active { background: var(--encre); color: var(--papier); border-color: var(--encre); }
.maquettes-recherche { margin-left: auto; }
.maquettes-recherche input { min-width: 220px; }
.maquettes-compte { margin: .6rem 0 .8rem; }

.maquettes-grille { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 1rem; }
.maquettes-grille > * { min-width: 0; }
.maquette-carte { border: 1px solid var(--trait); border-radius: 10px; overflow: hidden;
                  background: var(--carte); transition: border-color .15s, transform .15s; }
.maquette-carte:hover { border-color: var(--olive); transform: translateY(-2px); }
.maquette-carte__tete { display: flex; align-items: center; gap: .6rem; padding: .55rem .7rem; }
.maquette-carte__qui { min-width: 0; flex: 1; }
.maquette-carte__nom { display: block; font-weight: 600; font-size: .92rem; white-space: nowrap;
                       overflow: hidden; text-overflow: ellipsis; }
.maquette-carte__groupes { display: flex; gap: .25rem; flex-wrap: wrap; margin-top: .15rem; }

/* La vue : même format pour un poster, un aperçu ou une carte vide, pour que la grille
   reste régulière quoi qu'il arrive. */
.maquette-carte__vue { display: block; position: relative; border-top: 1px solid var(--trait);
                       background: #fff; aspect-ratio: 1 / 1.1; overflow: hidden; }
.maquette-carte__vue img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: top; }

/* L'aperçu vivant : le cadre est dessiné à 1200px de large, puis réduit. Le facteur suit la
   largeur de la carte grâce à une variable, recalculée nulle part — 0.21 convient de 250 à
   330px de carte, et l'aperçu n'a pas à être lisible, seulement reconnaissable. */
.maquette-carte__vue--apercu iframe { position: absolute; top: 0; left: 0; width: 1200px; height: 1500px;
                                      border: 0; transform: scale(.21); transform-origin: top left;
                                      pointer-events: none; background: #fff; }
.maquette-carte__vue--apercu > a { position: absolute; inset: 0; z-index: 1; }
.maquette-carte__vue--vide { display: grid; place-items: center; background: #fff; padding: 8%; }
.maquette-carte__vue--vide img { width: 100%; height: auto; object-fit: contain; opacity: .85; }

@media (max-width: 640px) {
  .maquettes-recherche { margin-left: 0; width: 100%; }
  .maquettes-recherche input { width: 100%; min-width: 0; }
  .maquettes-grille { grid-template-columns: 1fr; }
}

/* ===========================================================================
   CALENDRIER (etudiant/calendrier.php, v3.90)
   Une seule information domine : la prochaine séance. Le reste du semestre est
   lisible dessous, mais en retrait — même principe que la page « Projet ».
   =========================================================================== */
.cal-prochaine { border-left: 4px solid var(--accent); }
.cal-prochaine--aujourdhui { border-left-color: var(--accent-2); background: var(--confirmation-fond); }
.cal-prochaine__tp { font-size: 1.25rem; font-weight: 700; margin: .2rem 0 .1rem; }
.cal-prochaine__quand { margin: 0 0 .4rem; }
.cal-prochaine__note { margin: .5rem 0 0; color: var(--alerte-texte); font-weight: 600; }

/* « dans 8 jours » : une pastille discrète, lisible sans être criarde */
.cal-jours { display: inline-block; margin-left: .5rem; padding: .1em .55em; border-radius: 999px;
             background: var(--papier-2); color: var(--encre-douce); font-size: .78rem;
             font-weight: 700; white-space: nowrap; }

.cal-liste { width: 100%; border-collapse: collapse; }
.cal-liste th, .cal-liste td { padding: .45rem .6rem; text-align: left; vertical-align: top;
                               border-bottom: 1px solid var(--trait); }
.cal-liste thead th { font-size: .78rem; text-transform: uppercase; letter-spacing: .04em;
                      color: var(--encre-douce); }
.cal-liste tbody th { font-weight: 600; }
.cal-liste__etat { white-space: normal; font-size: .9rem; }
/* Une séance passée s'efface sans disparaître : on doit pouvoir la retrouver,
   mais elle ne doit pas concurrencer celles qui restent. */
.cal-liste__ligne--passee { color: var(--encre-douce); }
.cal-liste__ligne--passee th { font-weight: 400; }
.cal-liste__ligne--aujourdhui { background: var(--confirmation-fond); }
.cal-liste__ligne--aujourdhui th { font-weight: 700; }

.cal-reperes { list-style: none; margin: .4rem 0 .8rem; padding: 0; }
.cal-repere { display: flex; flex-wrap: wrap; align-items: baseline; gap: .2rem .7rem;
              padding: .4rem 0 .4rem .8rem; border-left: 3px solid var(--trait); }
.cal-repere__quand { min-width: 15rem; color: var(--encre-douce); font-size: .9rem; }
.cal-repere__quoi { font-weight: 600; }
.cal-repere--vacances { border-left-color: var(--mousse); }
.cal-repere--examen { border-left-color: var(--danger); }
.cal-repere--ferie { border-left-color: var(--accent-2); }
.cal-repere--annule { border-left-color: var(--alerte); }
.cal-repere--en-cours { background: var(--papier-2); }

@media (max-width: 640px) {
  .cal-repere__quand { min-width: 0; width: 100%; }
  .cal-liste thead { display: none; }
  .cal-liste tr { display: block; padding: .5rem 0; border-bottom: 1px solid var(--trait); }
  .cal-liste th, .cal-liste td { display: block; border: 0; padding: .1rem 0; }
}

/* Le rappel de séance sur « Mes TP » : une ligne, pas un encadré — la page en a déjà
   trois (frise, avancement, cartes) et une quatrième boîte la hacherait. */
.cal-rappel { display: flex; flex-wrap: wrap; align-items: baseline; gap: .35rem .6rem;
              margin: 0 0 1rem; padding: .5rem .8rem; border-left: 3px solid var(--accent);
              background: var(--papier-2); border-radius: 0 var(--rayon, 8px) var(--rayon, 8px) 0; }
.cal-rappel--aujourdhui { border-left-color: var(--accent-2); background: var(--confirmation-fond); }
.cal-rappel__lien { margin-left: auto; white-space: nowrap; }
.cal-rappel__note { flex-basis: 100%; color: var(--alerte-texte); font-weight: 600; font-size: .9rem; }

@media (max-width: 640px) {
  .cal-rappel__lien { margin-left: 0; }
}

/* --- La grille mois par mois (v3.92) ------------------------------------
   Des vrais <table> : une semaine est une ligne, un jour une cellule. Chaque
   couleur est doublee d'un texte lisible par lecteur d'ecran et d'une legende,
   parce qu'une couleur seule n'est pas une information. */
.cal-mois { display: flex; flex-wrap: wrap; gap: 1.2rem; margin: .8rem 0 1rem; }
.cal-grille { border-collapse: separate; border-spacing: 2px; }
.cal-grille caption { text-align: left; font-weight: 700; padding-bottom: .3rem; }
.cal-grille th { width: 2rem; padding: .15rem 0; font-size: .72rem; font-weight: 700;
                 color: var(--encre-douce); text-align: center; }
.cal-case { width: 2rem; height: 2.1rem; padding: 0; text-align: center; vertical-align: middle;
            border-radius: 5px; background: var(--papier-2); font-size: .8rem; line-height: 1; }
.cal-case--vide { background: none; }
.cal-case__num { display: block; }
.cal-case__tp { display: block; font-size: .58rem; font-weight: 700; letter-spacing: -.02em; }

/* Les repères teintent le fond ; la séance, elle, encadre — ainsi un jour qui
   porte les deux (le TP 7 tombe en pleine période de médians) montre les deux. */
/* Toutes les teintes sont TRANSLUCIDES et aucune n'impose la couleur du texte :
   le chiffre garde l'encre du thème, donc le contraste suit le clair comme le
   sombre. Une teinte opaque avec sa propre couleur de texte tombait à 1,65 de
   contraste en mode sombre — illisible. */
.cal-case--vacances       { background: rgba(91, 123, 58, .32); }
.cal-case--examen         { background: rgba(179, 38, 30, .22); }
.cal-case--ferie          { background: rgba(120, 90, 160, .28); }
.cal-case--annule         { background: rgba(183, 121, 31, .30); }
.cal-case--administratif  { background: rgba(120, 125, 135, .30); }
/* --accent-2 est le vert de la charte (#5B7B3A clair, #8FB364 sombre). J'avais
   d'abord ecrit var(--direct), qui n'existe pas : c'est un bout du nom de classe
   .indicateur--direct, pas une variable. Une var inconnue rend la declaration
   invalide, donc l'anneau ne s'affichait pas du tout, sans la moindre erreur. */
.cal-case--seance         { box-shadow: inset 0 0 0 2px var(--accent-2); font-weight: 700; }
.cal-case--aujourdhui     { outline: 2px solid var(--encre); outline-offset: 1px; }

.cal-legende { display: flex; flex-wrap: wrap; gap: .3rem 1.1rem; margin: 0; padding: 0;
               list-style: none; font-size: .82rem; color: var(--encre-douce); }
.cal-legende li { display: flex; align-items: center; gap: .35rem; }
/* La pastille ne pose PAS de fond : elle porte en plus une classe .cal-case--xxx,
   de meme specificite, et comme elle est declaree apres elle gagnerait — toutes les
   pastilles sortaient alors de la meme couleur. Le fond neutre est donc pose par
   .cal-pastille--neutre, pour la seule pastille qui n'a pas de teinte. */
.cal-pastille { display: inline-block; width: 1rem; height: 1rem; border-radius: 4px;
                flex: none; border: 1px solid var(--trait); }
.cal-pastille--neutre { background: var(--papier-2); }

@media (max-width: 640px) {
  .cal-mois { gap: 1rem; justify-content: center; }
}

/* --- Infobulle des cases (v3.93) ---------------------------------------
   Le title natif met une seconde a apparaitre, ne se style pas et ne sort
   jamais au clavier. On la fabrique donc, et on la montre aussi au focus :
   les cases qui portent une information sont focalisables. */
.cal-case[data-info] { position: relative; cursor: help; }
.cal-case[data-info]::after {
  content: attr(data-info);
  position: absolute; bottom: calc(100% + 6px); left: 50%; transform: translateX(-50%);
  z-index: 50; width: max-content; max-width: 15rem;
  padding: .4rem .6rem; border-radius: 6px;
  background: var(--inverse-fond); color: var(--inverse-texte);
  font-size: .78rem; font-weight: 400; line-height: 1.35; text-align: left;
  white-space: normal; box-shadow: var(--ombre);
  opacity: 0; visibility: hidden; transition: opacity .12s;
  pointer-events: none;
}
.cal-case[data-info]:hover::after,
.cal-case[data-info]:focus-visible::after { opacity: 1; visibility: visible; }
.cal-case[data-info]:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Les cases de la premiere ligne n'ont pas la place au-dessus : l'infobulle descend. */
.cal-grille tbody tr:first-child .cal-case[data-info]::after { bottom: auto; top: calc(100% + 6px); }
